• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media.tv.interactive;
18 
19 import android.annotation.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.tv.AdRequest;
27 import android.media.tv.AdResponse;
28 import android.media.tv.BroadcastInfoRequest;
29 import android.media.tv.BroadcastInfoResponse;
30 import android.media.tv.TvContentRating;
31 import android.media.tv.TvInputManager;
32 import android.media.tv.TvTrackInfo;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.Looper;
38 import android.os.Message;
39 import android.os.RemoteException;
40 import android.util.Log;
41 import android.util.Pools;
42 import android.util.SparseArray;
43 import android.view.InputChannel;
44 import android.view.InputEvent;
45 import android.view.InputEventSender;
46 import android.view.Surface;
47 import android.view.View;
48 
49 import com.android.internal.util.Preconditions;
50 
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 import java.util.Iterator;
54 import java.util.LinkedList;
55 import java.util.List;
56 import java.util.concurrent.Executor;
57 
58 /**
59  * Central system API to the overall TV interactive application framework (TIAF) architecture, which
60  * arbitrates interaction between Android applications and TV interactive apps.
61  */
62 @SystemService(Context.TV_INTERACTIVE_APP_SERVICE)
63 public final class TvInteractiveAppManager {
64     // TODO: cleanup and unhide public APIs
65     private static final String TAG = "TvInteractiveAppManager";
66 
67     /** @hide */
68     @Retention(RetentionPolicy.SOURCE)
69     @IntDef(flag = false, prefix = "SERVICE_STATE_", value = {
70             SERVICE_STATE_UNREALIZED,
71             SERVICE_STATE_PREPARING,
72             SERVICE_STATE_READY,
73             SERVICE_STATE_ERROR})
74     public @interface ServiceState {}
75 
76     /**
77      * Unrealized state of interactive app service.
78      */
79     public static final int SERVICE_STATE_UNREALIZED = 1;
80     /**
81      * Preparing state of interactive app service.
82      */
83     public static final int SERVICE_STATE_PREPARING = 2;
84     /**
85      * Ready state of interactive app service.
86      *
87      * <p>In this state, the interactive app service is ready, and interactive apps can be started.
88      *
89      * @see TvInteractiveAppView#startInteractiveApp()
90      */
91     public static final int SERVICE_STATE_READY = 3;
92     /**
93      * Error state of interactive app service.
94      */
95     public static final int SERVICE_STATE_ERROR = 4;
96 
97 
98     /** @hide */
99     @Retention(RetentionPolicy.SOURCE)
100     @IntDef(flag = false, prefix = "INTERACTIVE_APP_STATE_", value = {
101             INTERACTIVE_APP_STATE_STOPPED,
102             INTERACTIVE_APP_STATE_RUNNING,
103             INTERACTIVE_APP_STATE_ERROR})
104     public @interface InteractiveAppState {}
105 
106     /**
107      * Stopped (or not started) state of interactive application.
108      */
109     public static final int INTERACTIVE_APP_STATE_STOPPED = 1;
110     /**
111      * Running state of interactive application.
112      */
113     public static final int INTERACTIVE_APP_STATE_RUNNING = 2;
114     /**
115      * Error state of interactive application.
116      */
117     public static final int INTERACTIVE_APP_STATE_ERROR = 3;
118 
119 
120     /** @hide */
121     @Retention(RetentionPolicy.SOURCE)
122     @IntDef(flag = false, prefix = "ERROR_", value = {
123             ERROR_NONE,
124             ERROR_UNKNOWN,
125             ERROR_NOT_SUPPORTED,
126             ERROR_WEAK_SIGNAL,
127             ERROR_RESOURCE_UNAVAILABLE,
128             ERROR_BLOCKED,
129             ERROR_ENCRYPTED,
130             ERROR_UNKNOWN_CHANNEL,
131     })
132     public @interface ErrorCode {}
133 
134     /**
135      * No error.
136      */
137     public static final int ERROR_NONE = 0;
138     /**
139      * Unknown error code.
140      */
141     public static final int ERROR_UNKNOWN = 1;
142     /**
143      * Error code for an unsupported channel.
144      */
145     public static final int ERROR_NOT_SUPPORTED = 2;
146     /**
147      * Error code for weak signal.
148      */
149     public static final int ERROR_WEAK_SIGNAL = 3;
150     /**
151      * Error code when resource (e.g. tuner) is unavailable.
152      */
153     public static final int ERROR_RESOURCE_UNAVAILABLE = 4;
154     /**
155      * Error code for blocked contents.
156      */
157     public static final int ERROR_BLOCKED = 5;
158     /**
159      * Error code when the key or module is missing for the encrypted channel.
160      */
161     public static final int ERROR_ENCRYPTED = 6;
162     /**
163      * Error code when the current channel is an unknown channel.
164      */
165     public static final int ERROR_UNKNOWN_CHANNEL = 7;
166 
167     /** @hide */
168     @Retention(RetentionPolicy.SOURCE)
169     @IntDef(flag = false, prefix = "TELETEXT_APP_STATE_", value = {
170             TELETEXT_APP_STATE_SHOW,
171             TELETEXT_APP_STATE_HIDE,
172             TELETEXT_APP_STATE_ERROR})
173     public @interface TeletextAppState {}
174 
175     /**
176      * State of Teletext app: show
177      */
178     public static final int TELETEXT_APP_STATE_SHOW = 1;
179     /**
180      * State of Teletext app: hide
181      */
182     public static final int TELETEXT_APP_STATE_HIDE = 2;
183     /**
184      * State of Teletext app: error
185      */
186     public static final int TELETEXT_APP_STATE_ERROR = 3;
187 
188     /**
189      * Key for package name in app link.
190      * <p>Type: String
191      *
192      * @see #sendAppLinkCommand(String, Bundle)
193      */
194     public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
195 
196     /**
197      * Key for class name in app link.
198      * <p>Type: String
199      *
200      * @see #sendAppLinkCommand(String, Bundle)
201      */
202     public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
203 
204     /**
205      * Key for command type in app link command.
206      * <p>Type: String
207      *
208      * @see #sendAppLinkCommand(String, Bundle)
209      */
210     public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
211 
212     /**
213      * Key for service ID in app link command.
214      * <p>Type: String
215      *
216      * @see #sendAppLinkCommand(String, Bundle)
217      */
218     public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
219 
220     /**
221      * Key for back URI in app link command.
222      * <p>Type: String
223      *
224      * @see #sendAppLinkCommand(String, Bundle)
225      */
226     public static final String APP_LINK_KEY_BACK_URI = "back_uri";
227 
228     /**
229      * Broadcast intent action to send app command to TV app.
230      *
231      * @see #sendAppLinkCommand(String, Bundle)
232      */
233     public static final String ACTION_APP_LINK_COMMAND =
234             "android.media.tv.interactive.action.APP_LINK_COMMAND";
235 
236     /**
237      * Intent key for TV input ID. It's used to send app command to TV app.
238      * <p>Type: String
239      *
240      * @see #sendAppLinkCommand(String, Bundle)
241      * @see #ACTION_APP_LINK_COMMAND
242      */
243     public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
244 
245     /**
246      * Intent key for TV interactive app ID. It's used to send app command to TV app.
247      * <p>Type: String
248      *
249      * @see #sendAppLinkCommand(String, Bundle)
250      * @see #ACTION_APP_LINK_COMMAND
251      * @see TvInteractiveAppServiceInfo#getId()
252      */
253     public static final String INTENT_KEY_INTERACTIVE_APP_SERVICE_ID = "interactive_app_id";
254 
255     /**
256      * Intent key for TV channel URI. It's used to send app command to TV app.
257      * <p>Type: android.net.Uri
258      *
259      * @see #sendAppLinkCommand(String, Bundle)
260      * @see #ACTION_APP_LINK_COMMAND
261      */
262     public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
263 
264     /**
265      * Intent key for broadcast-independent(BI) interactive app type. It's used to send app command
266      * to TV app.
267      * <p>Type: int
268      *
269      * @see #sendAppLinkCommand(String, Bundle)
270      * @see #ACTION_APP_LINK_COMMAND
271      * @see android.media.tv.interactive.TvInteractiveAppServiceInfo#getSupportedTypes()
272      * @see android.media.tv.interactive.TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
273      */
274     public static final String INTENT_KEY_BI_INTERACTIVE_APP_TYPE = "bi_interactive_app_type";
275 
276     /**
277      * Intent key for broadcast-independent(BI) interactive app URI. It's used to send app command
278      * to TV app.
279      * <p>Type: android.net.Uri
280      *
281      * @see #sendAppLinkCommand(String, Bundle)
282      * @see #ACTION_APP_LINK_COMMAND
283      * @see android.media.tv.interactive.TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
284      */
285     public static final String INTENT_KEY_BI_INTERACTIVE_APP_URI = "bi_interactive_app_uri";
286 
287     /**
288      * Intent key for command type. It's used to send app command to TV app. The value of this key
289      * could vary according to TV apps.
290      * <p>Type: String
291      *
292      * @see #sendAppLinkCommand(String, Bundle)
293      * @see #ACTION_APP_LINK_COMMAND
294      */
295     public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
296 
297     private final ITvInteractiveAppManager mService;
298     private final int mUserId;
299 
300     // A mapping from the sequence number of a session to its SessionCallbackRecord.
301     private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
302             new SparseArray<>();
303 
304     // @GuardedBy("mLock")
305     private final List<TvInteractiveAppCallbackRecord> mCallbackRecords = new LinkedList<>();
306 
307     // A sequence number for the next session to be created. Should be protected by a lock
308     // {@code mSessionCallbackRecordMap}.
309     private int mNextSeq;
310 
311     private final Object mLock = new Object();
312 
313     private final ITvInteractiveAppClient mClient;
314 
315     /** @hide */
TvInteractiveAppManager(ITvInteractiveAppManager service, int userId)316     public TvInteractiveAppManager(ITvInteractiveAppManager service, int userId) {
317         mService = service;
318         mUserId = userId;
319         mClient = new ITvInteractiveAppClient.Stub() {
320             @Override
321             public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel,
322                     int seq) {
323                 synchronized (mSessionCallbackRecordMap) {
324                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
325                     if (record == null) {
326                         Log.e(TAG, "Callback not found for " + token);
327                         return;
328                     }
329                     Session session = null;
330                     if (token != null) {
331                         session = new Session(token, channel, mService, mUserId, seq,
332                                 mSessionCallbackRecordMap);
333                     } else {
334                         mSessionCallbackRecordMap.delete(seq);
335                     }
336                     record.postSessionCreated(session);
337                 }
338             }
339 
340             @Override
341             public void onSessionReleased(int seq) {
342                 synchronized (mSessionCallbackRecordMap) {
343                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
344                     mSessionCallbackRecordMap.delete(seq);
345                     if (record == null) {
346                         Log.e(TAG, "Callback not found for seq:" + seq);
347                         return;
348                     }
349                     record.mSession.releaseInternal();
350                     record.postSessionReleased();
351                 }
352             }
353 
354             @Override
355             public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
356                 synchronized (mSessionCallbackRecordMap) {
357                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
358                     if (record == null) {
359                         Log.e(TAG, "Callback not found for seq " + seq);
360                         return;
361                     }
362                     record.postLayoutSurface(left, top, right, bottom);
363                 }
364             }
365 
366             @Override
367             public void onBroadcastInfoRequest(BroadcastInfoRequest request, int seq) {
368                 synchronized (mSessionCallbackRecordMap) {
369                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
370                     if (record == null) {
371                         Log.e(TAG, "Callback not found for seq " + seq);
372                         return;
373                     }
374                     record.postBroadcastInfoRequest(request);
375                 }
376             }
377 
378             @Override
379             public void onRemoveBroadcastInfo(int requestId, int seq) {
380                 synchronized (mSessionCallbackRecordMap) {
381                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
382                     if (record == null) {
383                         Log.e(TAG, "Callback not found for seq " + seq);
384                         return;
385                     }
386                     record.postRemoveBroadcastInfo(requestId);
387                 }
388             }
389 
390             @Override
391             public void onCommandRequest(
392                     @TvInteractiveAppService.PlaybackCommandType String cmdType,
393                     Bundle parameters,
394                     int seq) {
395                 synchronized (mSessionCallbackRecordMap) {
396                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
397                     if (record == null) {
398                         Log.e(TAG, "Callback not found for seq " + seq);
399                         return;
400                     }
401                     record.postCommandRequest(cmdType, parameters);
402                 }
403             }
404 
405             @Override
406             public void onSetVideoBounds(Rect rect, int seq) {
407                 synchronized (mSessionCallbackRecordMap) {
408                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
409                     if (record == null) {
410                         Log.e(TAG, "Callback not found for seq " + seq);
411                         return;
412                     }
413                     record.postSetVideoBounds(rect);
414                 }
415             }
416 
417             @Override
418             public void onAdRequest(AdRequest request, int seq) {
419                 synchronized (mSessionCallbackRecordMap) {
420                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
421                     if (record == null) {
422                         Log.e(TAG, "Callback not found for seq " + seq);
423                         return;
424                     }
425                     record.postAdRequest(request);
426                 }
427             }
428 
429             @Override
430             public void onRequestCurrentChannelUri(int seq) {
431                 synchronized (mSessionCallbackRecordMap) {
432                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
433                     if (record == null) {
434                         Log.e(TAG, "Callback not found for seq " + seq);
435                         return;
436                     }
437                     record.postRequestCurrentChannelUri();
438                 }
439             }
440 
441             @Override
442             public void onRequestCurrentChannelLcn(int seq) {
443                 synchronized (mSessionCallbackRecordMap) {
444                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
445                     if (record == null) {
446                         Log.e(TAG, "Callback not found for seq " + seq);
447                         return;
448                     }
449                     record.postRequestCurrentChannelLcn();
450                 }
451             }
452 
453             @Override
454             public void onRequestStreamVolume(int seq) {
455                 synchronized (mSessionCallbackRecordMap) {
456                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
457                     if (record == null) {
458                         Log.e(TAG, "Callback not found for seq " + seq);
459                         return;
460                     }
461                     record.postRequestStreamVolume();
462                 }
463             }
464 
465             @Override
466             public void onRequestTrackInfoList(int seq) {
467                 synchronized (mSessionCallbackRecordMap) {
468                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
469                     if (record == null) {
470                         Log.e(TAG, "Callback not found for seq " + seq);
471                         return;
472                     }
473                     record.postRequestTrackInfoList();
474                 }
475             }
476 
477             @Override
478             public void onRequestCurrentTvInputId(int seq) {
479                 synchronized (mSessionCallbackRecordMap) {
480                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
481                     if (record == null) {
482                         Log.e(TAG, "Callback not found for seq " + seq);
483                         return;
484                     }
485                     record.postRequestCurrentTvInputId();
486                 }
487             }
488 
489             @Override
490             public void onRequestSigning(
491                     String id, String algorithm, String alias, byte[] data, int seq) {
492                 synchronized (mSessionCallbackRecordMap) {
493                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
494                     if (record == null) {
495                         Log.e(TAG, "Callback not found for seq " + seq);
496                         return;
497                     }
498                     record.postRequestSigning(id, algorithm, alias, data);
499                 }
500             }
501 
502             @Override
503             public void onSessionStateChanged(int state, int err, int seq) {
504                 synchronized (mSessionCallbackRecordMap) {
505                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
506                     if (record == null) {
507                         Log.e(TAG, "Callback not found for seq " + seq);
508                         return;
509                     }
510                     record.postSessionStateChanged(state, err);
511                 }
512             }
513 
514             @Override
515             public void onBiInteractiveAppCreated(Uri biIAppUri, String biIAppId, int seq) {
516                 synchronized (mSessionCallbackRecordMap) {
517                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
518                     if (record == null) {
519                         Log.e(TAG, "Callback not found for seq " + seq);
520                         return;
521                     }
522                     record.postBiInteractiveAppCreated(biIAppUri, biIAppId);
523                 }
524             }
525 
526             @Override
527             public void onTeletextAppStateChanged(int state, int seq) {
528                 synchronized (mSessionCallbackRecordMap) {
529                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
530                     if (record == null) {
531                         Log.e(TAG, "Callback not found for seq " + seq);
532                         return;
533                     }
534                     record.postTeletextAppStateChanged(state);
535                 }
536             }
537         };
538         ITvInteractiveAppManagerCallback managerCallback =
539                 new ITvInteractiveAppManagerCallback.Stub() {
540             @Override
541             public void onInteractiveAppServiceAdded(String iAppServiceId) {
542                 synchronized (mLock) {
543                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
544                         record.postInteractiveAppServiceAdded(iAppServiceId);
545                     }
546                 }
547             }
548 
549             @Override
550             public void onInteractiveAppServiceRemoved(String iAppServiceId) {
551                 synchronized (mLock) {
552                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
553                         record.postInteractiveAppServiceRemoved(iAppServiceId);
554                     }
555                 }
556             }
557 
558             @Override
559             public void onInteractiveAppServiceUpdated(String iAppServiceId) {
560                 synchronized (mLock) {
561                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
562                         record.postInteractiveAppServiceUpdated(iAppServiceId);
563                     }
564                 }
565             }
566 
567             @Override
568             public void onTvInteractiveAppServiceInfoUpdated(TvInteractiveAppServiceInfo iAppInfo) {
569                 // TODO: add public API updateInteractiveAppInfo()
570                 synchronized (mLock) {
571                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
572                         record.postTvInteractiveAppServiceInfoUpdated(iAppInfo);
573                     }
574                 }
575             }
576 
577             @Override
578             public void onStateChanged(String iAppServiceId, int type, int state, int err) {
579                 synchronized (mLock) {
580                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
581                         record.postStateChanged(iAppServiceId, type, state, err);
582                     }
583                 }
584             }
585         };
586         try {
587             if (mService != null) {
588                 mService.registerCallback(managerCallback, mUserId);
589             }
590         } catch (RemoteException e) {
591             throw e.rethrowFromSystemServer();
592         }
593     }
594 
595     /**
596      * Callback used to monitor status of the TV Interactive App.
597      */
598     public abstract static class TvInteractiveAppCallback {
599         /**
600          * This is called when a TV Interactive App service is added to the system.
601          *
602          * <p>Normally it happens when the user installs a new TV Interactive App service package
603          * that implements {@link TvInteractiveAppService} interface.
604          *
605          * @param iAppServiceId The ID of the TV Interactive App service.
606          */
onInteractiveAppServiceAdded(@onNull String iAppServiceId)607         public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) {
608         }
609 
610         /**
611          * This is called when a TV Interactive App service is removed from the system.
612          *
613          * <p>Normally it happens when the user uninstalls the previously installed TV Interactive
614          * App service package.
615          *
616          * @param iAppServiceId The ID of the TV Interactive App service.
617          */
onInteractiveAppServiceRemoved(@onNull String iAppServiceId)618         public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) {
619         }
620 
621         /**
622          * This is called when a TV Interactive App service is updated on the system.
623          *
624          * <p>Normally it happens when a previously installed TV Interactive App service package is
625          * re-installed or a newer version of the package exists becomes available/unavailable.
626          *
627          * @param iAppServiceId The ID of the TV Interactive App service.
628          */
onInteractiveAppServiceUpdated(@onNull String iAppServiceId)629         public void onInteractiveAppServiceUpdated(@NonNull String iAppServiceId) {
630         }
631 
632         /**
633          * This is called when the information about an existing TV Interactive App service has been
634          * updated.
635          *
636          * <p>Because the system automatically creates a <code>TvInteractiveAppServiceInfo</code>
637          * object for each TV Interactive App service based on the information collected from the
638          * <code>AndroidManifest.xml</code>, this method is only called back when such information
639          * has changed dynamically.
640          *
641          * @param iAppInfo The <code>TvInteractiveAppServiceInfo</code> object that contains new
642          *                 information.
643          * @hide
644          */
onTvInteractiveAppServiceInfoUpdated( @onNull TvInteractiveAppServiceInfo iAppInfo)645         public void onTvInteractiveAppServiceInfoUpdated(
646                 @NonNull TvInteractiveAppServiceInfo iAppInfo) {
647         }
648 
649         /**
650          * This is called when the state of the interactive app service is changed.
651          *
652          * @param iAppServiceId The ID of the TV Interactive App service.
653          * @param type the interactive app type
654          * @param state the current state of the service of the given type
655          * @param err the error code for error state. {@link #ERROR_NONE} is used when the state is
656          *            not {@link #SERVICE_STATE_ERROR}.
657          */
onTvInteractiveAppServiceStateChanged( @onNull String iAppServiceId, @TvInteractiveAppServiceInfo.InteractiveAppType int type, @ServiceState int state, @ErrorCode int err)658         public void onTvInteractiveAppServiceStateChanged(
659                 @NonNull String iAppServiceId,
660                 @TvInteractiveAppServiceInfo.InteractiveAppType int type,
661                 @ServiceState int state,
662                 @ErrorCode int err) {
663         }
664     }
665 
666     private static final class TvInteractiveAppCallbackRecord {
667         private final TvInteractiveAppCallback mCallback;
668         private final Executor mExecutor;
669 
TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor)670         TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor) {
671             mCallback = callback;
672             mExecutor = executor;
673         }
674 
getCallback()675         public TvInteractiveAppCallback getCallback() {
676             return mCallback;
677         }
678 
postInteractiveAppServiceAdded(final String iAppServiceId)679         public void postInteractiveAppServiceAdded(final String iAppServiceId) {
680             mExecutor.execute(new Runnable() {
681                 @Override
682                 public void run() {
683                     mCallback.onInteractiveAppServiceAdded(iAppServiceId);
684                 }
685             });
686         }
687 
postInteractiveAppServiceRemoved(final String iAppServiceId)688         public void postInteractiveAppServiceRemoved(final String iAppServiceId) {
689             mExecutor.execute(new Runnable() {
690                 @Override
691                 public void run() {
692                     mCallback.onInteractiveAppServiceRemoved(iAppServiceId);
693                 }
694             });
695         }
696 
postInteractiveAppServiceUpdated(final String iAppServiceId)697         public void postInteractiveAppServiceUpdated(final String iAppServiceId) {
698             mExecutor.execute(new Runnable() {
699                 @Override
700                 public void run() {
701                     mCallback.onInteractiveAppServiceUpdated(iAppServiceId);
702                 }
703             });
704         }
705 
postTvInteractiveAppServiceInfoUpdated( final TvInteractiveAppServiceInfo iAppInfo)706         public void postTvInteractiveAppServiceInfoUpdated(
707                 final TvInteractiveAppServiceInfo iAppInfo) {
708             mExecutor.execute(new Runnable() {
709                 @Override
710                 public void run() {
711                     mCallback.onTvInteractiveAppServiceInfoUpdated(iAppInfo);
712                 }
713             });
714         }
715 
postStateChanged(String iAppServiceId, int type, int state, int err)716         public void postStateChanged(String iAppServiceId, int type, int state, int err) {
717             mExecutor.execute(new Runnable() {
718                 @Override
719                 public void run() {
720                     mCallback.onTvInteractiveAppServiceStateChanged(
721                             iAppServiceId, type, state, err);
722                 }
723             });
724         }
725     }
726 
727     /**
728      * Creates a {@link Session} for a given TV interactive application.
729      *
730      * <p>The number of sessions that can be created at the same time is limited by the capability
731      * of the given interactive application.
732      *
733      * @param iAppServiceId The ID of the interactive application.
734      * @param type the type of the interactive application.
735      * @param callback A callback used to receive the created session.
736      * @param handler A {@link Handler} that the session creation will be delivered to.
737      * @hide
738      */
createSession(@onNull String iAppServiceId, int type, @NonNull final SessionCallback callback, @NonNull Handler handler)739     public void createSession(@NonNull String iAppServiceId, int type,
740             @NonNull final SessionCallback callback, @NonNull Handler handler) {
741         createSessionInternal(iAppServiceId, type, callback, handler);
742     }
743 
createSessionInternal(String iAppServiceId, int type, SessionCallback callback, Handler handler)744     private void createSessionInternal(String iAppServiceId, int type, SessionCallback callback,
745             Handler handler) {
746         Preconditions.checkNotNull(iAppServiceId);
747         Preconditions.checkNotNull(callback);
748         Preconditions.checkNotNull(handler);
749         SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
750         synchronized (mSessionCallbackRecordMap) {
751             int seq = mNextSeq++;
752             mSessionCallbackRecordMap.put(seq, record);
753             try {
754                 mService.createSession(mClient, iAppServiceId, type, seq, mUserId);
755             } catch (RemoteException e) {
756                 throw e.rethrowFromSystemServer();
757             }
758         }
759     }
760 
761     /**
762      * Returns the complete list of TV Interactive App service on the system.
763      *
764      * @return List of {@link TvInteractiveAppServiceInfo} for each TV Interactive App service that
765      *         describes its meta information.
766      */
767     @NonNull
getTvInteractiveAppServiceList()768     public List<TvInteractiveAppServiceInfo> getTvInteractiveAppServiceList() {
769         try {
770             return mService.getTvInteractiveAppServiceList(mUserId);
771         } catch (RemoteException e) {
772             throw e.rethrowFromSystemServer();
773         }
774     }
775 
776     /**
777      * Registers an Android application link info record which can be used to launch the specific
778      * Android application by TV interactive App RTE.
779      *
780      * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
781      *                        ID can be found in {@link TvInteractiveAppServiceInfo#getId()}.
782      * @param appLinkInfo The Android application link info record to be registered.
783      */
registerAppLinkInfo( @onNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo)784     public void registerAppLinkInfo(
785             @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
786         try {
787             mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
788         } catch (RemoteException e) {
789             throw e.rethrowFromSystemServer();
790         }
791     }
792 
793     /**
794      * Unregisters an Android application link info record which can be used to launch the specific
795      * Android application by TV interactive App RTE.
796      *
797      * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
798      *                        ID can be found in {@link TvInteractiveAppServiceInfo#getId()}.
799      * @param appLinkInfo The Android application link info record to be unregistered.
800      */
unregisterAppLinkInfo( @onNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo)801     public void unregisterAppLinkInfo(
802             @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
803         try {
804             mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
805         } catch (RemoteException e) {
806             throw e.rethrowFromSystemServer();
807         }
808     }
809 
810     /**
811      * Sends app link command.
812      *
813      * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
814      *                        ID can be found in {@link TvInteractiveAppServiceInfo#getId()}.
815      * @param command The command to be sent.
816      */
sendAppLinkCommand(@onNull String tvIAppServiceId, @NonNull Bundle command)817     public void sendAppLinkCommand(@NonNull String tvIAppServiceId, @NonNull Bundle command) {
818         try {
819             mService.sendAppLinkCommand(tvIAppServiceId, command, mUserId);
820         } catch (RemoteException e) {
821             throw e.rethrowFromSystemServer();
822         }
823     }
824 
825     /**
826      * Registers a {@link TvInteractiveAppCallback}.
827      *
828      * @param callback A callback used to monitor status of the TV Interactive App services.
829      * @param executor A {@link Executor} that the status change will be delivered to.
830      */
registerCallback( @allbackExecutor @onNull Executor executor, @NonNull TvInteractiveAppCallback callback)831     public void registerCallback(
832             @CallbackExecutor @NonNull Executor executor,
833             @NonNull TvInteractiveAppCallback callback) {
834         Preconditions.checkNotNull(callback);
835         Preconditions.checkNotNull(executor);
836         synchronized (mLock) {
837             mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, executor));
838         }
839     }
840 
841     /**
842      * Unregisters the existing {@link TvInteractiveAppCallback}.
843      *
844      * @param callback The existing callback to remove.
845      */
unregisterCallback(@onNull final TvInteractiveAppCallback callback)846     public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) {
847         Preconditions.checkNotNull(callback);
848         synchronized (mLock) {
849             for (Iterator<TvInteractiveAppCallbackRecord> it = mCallbackRecords.iterator();
850                     it.hasNext(); ) {
851                 TvInteractiveAppCallbackRecord record = it.next();
852                 if (record.getCallback() == callback) {
853                     it.remove();
854                     break;
855                 }
856             }
857         }
858     }
859 
860     /**
861      * The Session provides the per-session functionality of interactive app.
862      * @hide
863      */
864     public static final class Session {
865         static final int DISPATCH_IN_PROGRESS = -1;
866         static final int DISPATCH_NOT_HANDLED = 0;
867         static final int DISPATCH_HANDLED = 1;
868 
869         private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
870 
871         private final ITvInteractiveAppManager mService;
872         private final int mUserId;
873         private final int mSeq;
874         private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
875 
876         // For scheduling input event handling on the main thread. This also serves as a lock to
877         // protect pending input events and the input channel.
878         private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
879 
880         private TvInputManager.Session mInputSession;
881         private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20);
882         private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
883 
884         private IBinder mToken;
885         private TvInputEventSender mSender;
886         private InputChannel mInputChannel;
887 
Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service, int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap)888         private Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service,
889                 int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
890             mToken = token;
891             mInputChannel = channel;
892             mService = service;
893             mUserId = userId;
894             mSeq = seq;
895             mSessionCallbackRecordMap = sessionCallbackRecordMap;
896         }
897 
getInputSession()898         public TvInputManager.Session getInputSession() {
899             return mInputSession;
900         }
901 
setInputSession(TvInputManager.Session inputSession)902         public void setInputSession(TvInputManager.Session inputSession) {
903             mInputSession = inputSession;
904         }
905 
startInteractiveApp()906         void startInteractiveApp() {
907             if (mToken == null) {
908                 Log.w(TAG, "The session has been already released");
909                 return;
910             }
911             try {
912                 mService.startInteractiveApp(mToken, mUserId);
913             } catch (RemoteException e) {
914                 throw e.rethrowFromSystemServer();
915             }
916         }
917 
stopInteractiveApp()918         void stopInteractiveApp() {
919             if (mToken == null) {
920                 Log.w(TAG, "The session has been already released");
921                 return;
922             }
923             try {
924                 mService.stopInteractiveApp(mToken, mUserId);
925             } catch (RemoteException e) {
926                 throw e.rethrowFromSystemServer();
927             }
928         }
929 
resetInteractiveApp()930         void resetInteractiveApp() {
931             if (mToken == null) {
932                 Log.w(TAG, "The session has been already released");
933                 return;
934             }
935             try {
936                 mService.resetInteractiveApp(mToken, mUserId);
937             } catch (RemoteException e) {
938                 throw e.rethrowFromSystemServer();
939             }
940         }
941 
createBiInteractiveApp(Uri biIAppUri, Bundle params)942         void createBiInteractiveApp(Uri biIAppUri, Bundle params) {
943             if (mToken == null) {
944                 Log.w(TAG, "The session has been already released");
945                 return;
946             }
947             try {
948                 mService.createBiInteractiveApp(mToken, biIAppUri, params, mUserId);
949             } catch (RemoteException e) {
950                 throw e.rethrowFromSystemServer();
951             }
952         }
953 
destroyBiInteractiveApp(String biIAppId)954         void destroyBiInteractiveApp(String biIAppId) {
955             if (mToken == null) {
956                 Log.w(TAG, "The session has been already released");
957                 return;
958             }
959             try {
960                 mService.destroyBiInteractiveApp(mToken, biIAppId, mUserId);
961             } catch (RemoteException e) {
962                 throw e.rethrowFromSystemServer();
963             }
964         }
965 
setTeletextAppEnabled(boolean enable)966         void setTeletextAppEnabled(boolean enable) {
967             if (mToken == null) {
968                 Log.w(TAG, "The session has been already released");
969                 return;
970             }
971             try {
972                 mService.setTeletextAppEnabled(mToken, enable, mUserId);
973             } catch (RemoteException e) {
974                 throw e.rethrowFromSystemServer();
975             }
976         }
977 
sendCurrentChannelUri(@ullable Uri channelUri)978         void sendCurrentChannelUri(@Nullable Uri channelUri) {
979             if (mToken == null) {
980                 Log.w(TAG, "The session has been already released");
981                 return;
982             }
983             try {
984                 mService.sendCurrentChannelUri(mToken, channelUri, mUserId);
985             } catch (RemoteException e) {
986                 throw e.rethrowFromSystemServer();
987             }
988         }
989 
sendCurrentChannelLcn(int lcn)990         void sendCurrentChannelLcn(int lcn) {
991             if (mToken == null) {
992                 Log.w(TAG, "The session has been already released");
993                 return;
994             }
995             try {
996                 mService.sendCurrentChannelLcn(mToken, lcn, mUserId);
997             } catch (RemoteException e) {
998                 throw e.rethrowFromSystemServer();
999             }
1000         }
1001 
sendStreamVolume(float volume)1002         void sendStreamVolume(float volume) {
1003             if (mToken == null) {
1004                 Log.w(TAG, "The session has been already released");
1005                 return;
1006             }
1007             try {
1008                 mService.sendStreamVolume(mToken, volume, mUserId);
1009             } catch (RemoteException e) {
1010                 throw e.rethrowFromSystemServer();
1011             }
1012         }
1013 
sendTrackInfoList(@onNull List<TvTrackInfo> tracks)1014         void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
1015             if (mToken == null) {
1016                 Log.w(TAG, "The session has been already released");
1017                 return;
1018             }
1019             try {
1020                 mService.sendTrackInfoList(mToken, tracks, mUserId);
1021             } catch (RemoteException e) {
1022                 throw e.rethrowFromSystemServer();
1023             }
1024         }
1025 
sendCurrentTvInputId(@ullable String inputId)1026         void sendCurrentTvInputId(@Nullable String inputId) {
1027             if (mToken == null) {
1028                 Log.w(TAG, "The session has been already released");
1029                 return;
1030             }
1031             try {
1032                 mService.sendCurrentTvInputId(mToken, inputId, mUserId);
1033             } catch (RemoteException e) {
1034                 throw e.rethrowFromSystemServer();
1035             }
1036         }
1037 
sendSigningResult(@onNull String signingId, @NonNull byte[] result)1038         void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
1039             if (mToken == null) {
1040                 Log.w(TAG, "The session has been already released");
1041                 return;
1042             }
1043             try {
1044                 mService.sendSigningResult(mToken, signingId, result, mUserId);
1045             } catch (RemoteException e) {
1046                 throw e.rethrowFromSystemServer();
1047             }
1048         }
1049 
notifyError(@onNull String errMsg, @NonNull Bundle params)1050         void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
1051             if (mToken == null) {
1052                 Log.w(TAG, "The session has been already released");
1053                 return;
1054             }
1055             try {
1056                 mService.notifyError(mToken, errMsg, params, mUserId);
1057             } catch (RemoteException e) {
1058                 throw e.rethrowFromSystemServer();
1059             }
1060         }
1061 
1062         /**
1063          * Sets the {@link android.view.Surface} for this session.
1064          *
1065          * @param surface A {@link android.view.Surface} used to render video.
1066          */
setSurface(Surface surface)1067         public void setSurface(Surface surface) {
1068             if (mToken == null) {
1069                 Log.w(TAG, "The session has been already released");
1070                 return;
1071             }
1072             // surface can be null.
1073             try {
1074                 mService.setSurface(mToken, surface, mUserId);
1075             } catch (RemoteException e) {
1076                 throw e.rethrowFromSystemServer();
1077             }
1078         }
1079 
1080         /**
1081          * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
1082          * should be called whenever the layout of its containing view is changed.
1083          * {@link #removeMediaView()} should be called to remove the media view.
1084          * Since a session can have only one media view, this method should be called only once
1085          * or it can be called again after calling {@link #removeMediaView()}.
1086          *
1087          * @param view A view for interactive app.
1088          * @param frame A position of the media view.
1089          * @throws IllegalStateException if {@code view} is not attached to a window.
1090          */
createMediaView(@onNull View view, @NonNull Rect frame)1091         void createMediaView(@NonNull View view, @NonNull Rect frame) {
1092             Preconditions.checkNotNull(view);
1093             Preconditions.checkNotNull(frame);
1094             if (view.getWindowToken() == null) {
1095                 throw new IllegalStateException("view must be attached to a window");
1096             }
1097             if (mToken == null) {
1098                 Log.w(TAG, "The session has been already released");
1099                 return;
1100             }
1101             try {
1102                 mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
1103             } catch (RemoteException e) {
1104                 throw e.rethrowFromSystemServer();
1105             }
1106         }
1107 
1108         /**
1109          * Relayouts the current media view.
1110          *
1111          * @param frame A new position of the media view.
1112          */
relayoutMediaView(@onNull Rect frame)1113         void relayoutMediaView(@NonNull Rect frame) {
1114             Preconditions.checkNotNull(frame);
1115             if (mToken == null) {
1116                 Log.w(TAG, "The session has been already released");
1117                 return;
1118             }
1119             try {
1120                 mService.relayoutMediaView(mToken, frame, mUserId);
1121             } catch (RemoteException e) {
1122                 throw e.rethrowFromSystemServer();
1123             }
1124         }
1125 
1126         /**
1127          * Removes the current media view.
1128          */
removeMediaView()1129         void removeMediaView() {
1130             if (mToken == null) {
1131                 Log.w(TAG, "The session has been already released");
1132                 return;
1133             }
1134             try {
1135                 mService.removeMediaView(mToken, mUserId);
1136             } catch (RemoteException e) {
1137                 throw e.rethrowFromSystemServer();
1138             }
1139         }
1140 
1141         /**
1142          * Notifies of any structural changes (format or size) of the surface passed in
1143          * {@link #setSurface}.
1144          *
1145          * @param format The new PixelFormat of the surface.
1146          * @param width The new width of the surface.
1147          * @param height The new height of the surface.
1148          */
dispatchSurfaceChanged(int format, int width, int height)1149         public void dispatchSurfaceChanged(int format, int width, int height) {
1150             if (mToken == null) {
1151                 Log.w(TAG, "The session has been already released");
1152                 return;
1153             }
1154             try {
1155                 mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
1156             } catch (RemoteException e) {
1157                 throw e.rethrowFromSystemServer();
1158             }
1159         }
1160 
1161         /**
1162          * Dispatches an input event to this session.
1163          *
1164          * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
1165          * @param token A token used to identify the input event later in the callback.
1166          * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
1167          * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
1168          *            {@code null}.
1169          * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
1170          *         {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
1171          *         {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
1172          *         be invoked later.
1173          * @hide
1174          */
dispatchInputEvent(@onNull InputEvent event, Object token, @NonNull FinishedInputEventCallback callback, @NonNull Handler handler)1175         public int dispatchInputEvent(@NonNull InputEvent event, Object token,
1176                 @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
1177             Preconditions.checkNotNull(event);
1178             Preconditions.checkNotNull(callback);
1179             Preconditions.checkNotNull(handler);
1180             synchronized (mHandler) {
1181                 if (mInputChannel == null) {
1182                     return DISPATCH_NOT_HANDLED;
1183                 }
1184                 PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
1185                 if (Looper.myLooper() == Looper.getMainLooper()) {
1186                     // Already running on the main thread so we can send the event immediately.
1187                     return sendInputEventOnMainLooperLocked(p);
1188                 }
1189 
1190                 // Post the event to the main thread.
1191                 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
1192                 msg.setAsynchronous(true);
1193                 mHandler.sendMessage(msg);
1194                 return DISPATCH_IN_PROGRESS;
1195             }
1196         }
1197 
1198         /**
1199          * Notifies of any broadcast info response passed in from TIS.
1200          *
1201          * @param response response passed in from TIS.
1202          */
notifyBroadcastInfoResponse(BroadcastInfoResponse response)1203         public void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
1204             if (mToken == null) {
1205                 Log.w(TAG, "The session has been already released");
1206                 return;
1207             }
1208             try {
1209                 mService.notifyBroadcastInfoResponse(mToken, response, mUserId);
1210             } catch (RemoteException e) {
1211                 throw e.rethrowFromSystemServer();
1212             }
1213         }
1214 
1215         /**
1216          * Notifies of any advertisement response passed in from TIS.
1217          *
1218          * @param response response passed in from TIS.
1219          */
notifyAdResponse(AdResponse response)1220         public void notifyAdResponse(AdResponse response) {
1221             if (mToken == null) {
1222                 Log.w(TAG, "The session has been already released");
1223                 return;
1224             }
1225             try {
1226                 mService.notifyAdResponse(mToken, response, mUserId);
1227             } catch (RemoteException e) {
1228                 throw e.rethrowFromSystemServer();
1229             }
1230         }
1231 
1232         /**
1233          * Releases this session.
1234          */
release()1235         public void release() {
1236             if (mToken == null) {
1237                 Log.w(TAG, "The session has been already released");
1238                 return;
1239             }
1240             try {
1241                 mService.releaseSession(mToken, mUserId);
1242             } catch (RemoteException e) {
1243                 throw e.rethrowFromSystemServer();
1244             }
1245 
1246             releaseInternal();
1247         }
1248 
1249         /**
1250          * Notifies Interactive APP session when a channel is tuned.
1251          */
notifyTuned(Uri channelUri)1252         public void notifyTuned(Uri channelUri) {
1253             if (mToken == null) {
1254                 Log.w(TAG, "The session has been already released");
1255                 return;
1256             }
1257             try {
1258                 mService.notifyTuned(mToken, channelUri, mUserId);
1259             } catch (RemoteException e) {
1260                 throw e.rethrowFromSystemServer();
1261             }
1262         }
1263 
1264         /**
1265          * Notifies Interactive APP session when a track is selected.
1266          */
notifyTrackSelected(int type, String trackId)1267         public void notifyTrackSelected(int type, String trackId) {
1268             if (mToken == null) {
1269                 Log.w(TAG, "The session has been already released");
1270                 return;
1271             }
1272             try {
1273                 mService.notifyTrackSelected(mToken, type, trackId, mUserId);
1274             } catch (RemoteException e) {
1275                 throw e.rethrowFromSystemServer();
1276             }
1277         }
1278 
1279         /**
1280          * Notifies Interactive APP session when tracks are changed.
1281          */
notifyTracksChanged(List<TvTrackInfo> tracks)1282         public void notifyTracksChanged(List<TvTrackInfo> tracks) {
1283             if (mToken == null) {
1284                 Log.w(TAG, "The session has been already released");
1285                 return;
1286             }
1287             try {
1288                 mService.notifyTracksChanged(mToken, tracks, mUserId);
1289             } catch (RemoteException e) {
1290                 throw e.rethrowFromSystemServer();
1291             }
1292         }
1293 
1294         /**
1295          * Notifies Interactive APP session when video is available.
1296          */
notifyVideoAvailable()1297         public void notifyVideoAvailable() {
1298             if (mToken == null) {
1299                 Log.w(TAG, "The session has been already released");
1300                 return;
1301             }
1302             try {
1303                 mService.notifyVideoAvailable(mToken, mUserId);
1304             } catch (RemoteException e) {
1305                 throw e.rethrowFromSystemServer();
1306             }
1307         }
1308 
1309         /**
1310          * Notifies Interactive APP session when video is unavailable.
1311          */
notifyVideoUnavailable(int reason)1312         public void notifyVideoUnavailable(int reason) {
1313             if (mToken == null) {
1314                 Log.w(TAG, "The session has been already released");
1315                 return;
1316             }
1317             try {
1318                 mService.notifyVideoUnavailable(mToken, reason, mUserId);
1319             } catch (RemoteException e) {
1320                 throw e.rethrowFromSystemServer();
1321             }
1322         }
1323 
1324         /**
1325          * Notifies Interactive APP session when content is allowed.
1326          */
notifyContentAllowed()1327         public void notifyContentAllowed() {
1328             if (mToken == null) {
1329                 Log.w(TAG, "The session has been already released");
1330                 return;
1331             }
1332             try {
1333                 mService.notifyContentAllowed(mToken, mUserId);
1334             } catch (RemoteException e) {
1335                 throw e.rethrowFromSystemServer();
1336             }
1337         }
1338 
1339         /**
1340          * Notifies Interactive APP session when content is blocked.
1341          */
notifyContentBlocked(TvContentRating rating)1342         public void notifyContentBlocked(TvContentRating rating) {
1343             if (mToken == null) {
1344                 Log.w(TAG, "The session has been already released");
1345                 return;
1346             }
1347             try {
1348                 mService.notifyContentBlocked(mToken, rating.flattenToString(), mUserId);
1349             } catch (RemoteException e) {
1350                 throw e.rethrowFromSystemServer();
1351             }
1352         }
1353 
1354         /**
1355          * Notifies Interactive APP session when signal strength is changed.
1356          */
notifySignalStrength(int strength)1357         public void notifySignalStrength(int strength) {
1358             if (mToken == null) {
1359                 Log.w(TAG, "The session has been already released");
1360                 return;
1361             }
1362             try {
1363                 mService.notifySignalStrength(mToken, strength, mUserId);
1364             } catch (RemoteException e) {
1365                 throw e.rethrowFromSystemServer();
1366             }
1367         }
1368 
flushPendingEventsLocked()1369         private void flushPendingEventsLocked() {
1370             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
1371 
1372             final int count = mPendingEvents.size();
1373             for (int i = 0; i < count; i++) {
1374                 int seq = mPendingEvents.keyAt(i);
1375                 Message msg = mHandler.obtainMessage(
1376                         InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
1377                 msg.setAsynchronous(true);
1378                 msg.sendToTarget();
1379             }
1380         }
1381 
releaseInternal()1382         private void releaseInternal() {
1383             mToken = null;
1384             synchronized (mHandler) {
1385                 if (mInputChannel != null) {
1386                     if (mSender != null) {
1387                         flushPendingEventsLocked();
1388                         mSender.dispose();
1389                         mSender = null;
1390                     }
1391                     mInputChannel.dispose();
1392                     mInputChannel = null;
1393                 }
1394             }
1395             synchronized (mSessionCallbackRecordMap) {
1396                 mSessionCallbackRecordMap.delete(mSeq);
1397             }
1398         }
1399 
obtainPendingEventLocked(InputEvent event, Object token, FinishedInputEventCallback callback, Handler handler)1400         private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
1401                 FinishedInputEventCallback callback, Handler handler) {
1402             PendingEvent p = mPendingEventPool.acquire();
1403             if (p == null) {
1404                 p = new PendingEvent();
1405             }
1406             p.mEvent = event;
1407             p.mEventToken = token;
1408             p.mCallback = callback;
1409             p.mEventHandler = handler;
1410             return p;
1411         }
1412 
1413         // Assumes the event has already been removed from the queue.
invokeFinishedInputEventCallback(PendingEvent p, boolean handled)1414         void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
1415             p.mHandled = handled;
1416             if (p.mEventHandler.getLooper().isCurrentThread()) {
1417                 // Already running on the callback handler thread so we can send the callback
1418                 // immediately.
1419                 p.run();
1420             } else {
1421                 // Post the event to the callback handler thread.
1422                 // In this case, the callback will be responsible for recycling the event.
1423                 Message msg = Message.obtain(p.mEventHandler, p);
1424                 msg.setAsynchronous(true);
1425                 msg.sendToTarget();
1426             }
1427         }
1428 
1429         // Must be called on the main looper
sendInputEventAndReportResultOnMainLooper(PendingEvent p)1430         private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
1431             synchronized (mHandler) {
1432                 int result = sendInputEventOnMainLooperLocked(p);
1433                 if (result == DISPATCH_IN_PROGRESS) {
1434                     return;
1435                 }
1436             }
1437 
1438             invokeFinishedInputEventCallback(p, false);
1439         }
1440 
sendInputEventOnMainLooperLocked(PendingEvent p)1441         private int sendInputEventOnMainLooperLocked(PendingEvent p) {
1442             if (mInputChannel != null) {
1443                 if (mSender == null) {
1444                     mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper());
1445                 }
1446 
1447                 final InputEvent event = p.mEvent;
1448                 final int seq = event.getSequenceNumber();
1449                 if (mSender.sendInputEvent(seq, event)) {
1450                     mPendingEvents.put(seq, p);
1451                     Message msg = mHandler.obtainMessage(
1452                             InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
1453                     msg.setAsynchronous(true);
1454                     mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
1455                     return DISPATCH_IN_PROGRESS;
1456                 }
1457 
1458                 Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
1459                         + event);
1460             }
1461             return DISPATCH_NOT_HANDLED;
1462         }
1463 
finishedInputEvent(int seq, boolean handled, boolean timeout)1464         void finishedInputEvent(int seq, boolean handled, boolean timeout) {
1465             final PendingEvent p;
1466             synchronized (mHandler) {
1467                 int index = mPendingEvents.indexOfKey(seq);
1468                 if (index < 0) {
1469                     return; // spurious, event already finished or timed out
1470                 }
1471 
1472                 p = mPendingEvents.valueAt(index);
1473                 mPendingEvents.removeAt(index);
1474 
1475                 if (timeout) {
1476                     Log.w(TAG, "Timeout waiting for session to handle input event after "
1477                             + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
1478                 } else {
1479                     mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
1480                 }
1481             }
1482 
1483             invokeFinishedInputEventCallback(p, handled);
1484         }
1485 
recyclePendingEventLocked(PendingEvent p)1486         private void recyclePendingEventLocked(PendingEvent p) {
1487             p.recycle();
1488             mPendingEventPool.release(p);
1489         }
1490 
1491         /**
1492          * Callback that is invoked when an input event that was dispatched to this session has been
1493          * finished.
1494          *
1495          * @hide
1496          */
1497         public interface FinishedInputEventCallback {
1498             /**
1499              * Called when the dispatched input event is finished.
1500              *
1501              * @param token A token passed to {@link #dispatchInputEvent}.
1502              * @param handled {@code true} if the dispatched input event was handled properly.
1503              *            {@code false} otherwise.
1504              */
onFinishedInputEvent(Object token, boolean handled)1505             void onFinishedInputEvent(Object token, boolean handled);
1506         }
1507 
1508         private final class InputEventHandler extends Handler {
1509             public static final int MSG_SEND_INPUT_EVENT = 1;
1510             public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
1511             public static final int MSG_FLUSH_INPUT_EVENT = 3;
1512 
InputEventHandler(Looper looper)1513             InputEventHandler(Looper looper) {
1514                 super(looper, null, true);
1515             }
1516 
1517             @Override
handleMessage(Message msg)1518             public void handleMessage(Message msg) {
1519                 switch (msg.what) {
1520                     case MSG_SEND_INPUT_EVENT: {
1521                         sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
1522                         return;
1523                     }
1524                     case MSG_TIMEOUT_INPUT_EVENT: {
1525                         finishedInputEvent(msg.arg1, false, true);
1526                         return;
1527                     }
1528                     case MSG_FLUSH_INPUT_EVENT: {
1529                         finishedInputEvent(msg.arg1, false, false);
1530                         return;
1531                     }
1532                 }
1533             }
1534         }
1535 
1536         private final class TvInputEventSender extends InputEventSender {
TvInputEventSender(InputChannel inputChannel, Looper looper)1537             TvInputEventSender(InputChannel inputChannel, Looper looper) {
1538                 super(inputChannel, looper);
1539             }
1540 
1541             @Override
onInputEventFinished(int seq, boolean handled)1542             public void onInputEventFinished(int seq, boolean handled) {
1543                 finishedInputEvent(seq, handled, false);
1544             }
1545         }
1546 
1547         private final class PendingEvent implements Runnable {
1548             public InputEvent mEvent;
1549             public Object mEventToken;
1550             public FinishedInputEventCallback mCallback;
1551             public Handler mEventHandler;
1552             public boolean mHandled;
1553 
recycle()1554             public void recycle() {
1555                 mEvent = null;
1556                 mEventToken = null;
1557                 mCallback = null;
1558                 mEventHandler = null;
1559                 mHandled = false;
1560             }
1561 
1562             @Override
run()1563             public void run() {
1564                 mCallback.onFinishedInputEvent(mEventToken, mHandled);
1565 
1566                 synchronized (mEventHandler) {
1567                     recyclePendingEventLocked(this);
1568                 }
1569             }
1570         }
1571     }
1572 
1573     private static final class SessionCallbackRecord {
1574         private final SessionCallback mSessionCallback;
1575         private final Handler mHandler;
1576         private Session mSession;
1577 
SessionCallbackRecord(SessionCallback sessionCallback, Handler handler)1578         SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) {
1579             mSessionCallback = sessionCallback;
1580             mHandler = handler;
1581         }
1582 
postSessionCreated(final Session session)1583         void postSessionCreated(final Session session) {
1584             mSession = session;
1585             mHandler.post(new Runnable() {
1586                 @Override
1587                 public void run() {
1588                     mSessionCallback.onSessionCreated(session);
1589                 }
1590             });
1591         }
1592 
postSessionReleased()1593         void postSessionReleased() {
1594             mHandler.post(new Runnable() {
1595                 @Override
1596                 public void run() {
1597                     mSessionCallback.onSessionReleased(mSession);
1598                 }
1599             });
1600         }
1601 
postLayoutSurface(final int left, final int top, final int right, final int bottom)1602         void postLayoutSurface(final int left, final int top, final int right,
1603                 final int bottom) {
1604             mHandler.post(new Runnable() {
1605                 @Override
1606                 public void run() {
1607                     mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
1608                 }
1609             });
1610         }
1611 
postBroadcastInfoRequest(final BroadcastInfoRequest request)1612         void postBroadcastInfoRequest(final BroadcastInfoRequest request) {
1613             mHandler.post(new Runnable() {
1614                 @Override
1615                 public void run() {
1616                     mSession.getInputSession().requestBroadcastInfo(request);
1617                 }
1618             });
1619         }
1620 
postRemoveBroadcastInfo(final int requestId)1621         void postRemoveBroadcastInfo(final int requestId) {
1622             mHandler.post(new Runnable() {
1623                 @Override
1624                 public void run() {
1625                     mSession.getInputSession().removeBroadcastInfo(requestId);
1626                 }
1627             });
1628         }
1629 
postCommandRequest( final @TvInteractiveAppService.PlaybackCommandType String cmdType, final Bundle parameters)1630         void postCommandRequest(
1631                 final @TvInteractiveAppService.PlaybackCommandType String cmdType,
1632                 final Bundle parameters) {
1633             mHandler.post(new Runnable() {
1634                 @Override
1635                 public void run() {
1636                     mSessionCallback.onCommandRequest(mSession, cmdType, parameters);
1637                 }
1638             });
1639         }
1640 
postSetVideoBounds(Rect rect)1641         void postSetVideoBounds(Rect rect) {
1642             mHandler.post(new Runnable() {
1643                 @Override
1644                 public void run() {
1645                     mSessionCallback.onSetVideoBounds(mSession, rect);
1646                 }
1647             });
1648         }
1649 
postRequestCurrentChannelUri()1650         void postRequestCurrentChannelUri() {
1651             mHandler.post(new Runnable() {
1652                 @Override
1653                 public void run() {
1654                     mSessionCallback.onRequestCurrentChannelUri(mSession);
1655                 }
1656             });
1657         }
1658 
postRequestCurrentChannelLcn()1659         void postRequestCurrentChannelLcn() {
1660             mHandler.post(new Runnable() {
1661                 @Override
1662                 public void run() {
1663                     mSessionCallback.onRequestCurrentChannelLcn(mSession);
1664                 }
1665             });
1666         }
1667 
postRequestStreamVolume()1668         void postRequestStreamVolume() {
1669             mHandler.post(new Runnable() {
1670                 @Override
1671                 public void run() {
1672                     mSessionCallback.onRequestStreamVolume(mSession);
1673                 }
1674             });
1675         }
1676 
postRequestTrackInfoList()1677         void postRequestTrackInfoList() {
1678             mHandler.post(new Runnable() {
1679                 @Override
1680                 public void run() {
1681                     mSessionCallback.onRequestTrackInfoList(mSession);
1682                 }
1683             });
1684         }
1685 
postRequestCurrentTvInputId()1686         void postRequestCurrentTvInputId() {
1687             mHandler.post(new Runnable() {
1688                 @Override
1689                 public void run() {
1690                     mSessionCallback.onRequestCurrentTvInputId(mSession);
1691                 }
1692             });
1693         }
1694 
postRequestSigning(String id, String algorithm, String alias, byte[] data)1695         void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
1696             mHandler.post(new Runnable() {
1697                 @Override
1698                 public void run() {
1699                     mSessionCallback.onRequestSigning(mSession, id, algorithm, alias, data);
1700                 }
1701             });
1702         }
1703 
postAdRequest(final AdRequest request)1704         void postAdRequest(final AdRequest request) {
1705             mHandler.post(new Runnable() {
1706                 @Override
1707                 public void run() {
1708                     if (mSession.getInputSession() != null) {
1709                         mSession.getInputSession().requestAd(request);
1710                     }
1711                 }
1712             });
1713         }
1714 
postSessionStateChanged(int state, int err)1715         void postSessionStateChanged(int state, int err) {
1716             mHandler.post(new Runnable() {
1717                 @Override
1718                 public void run() {
1719                     mSessionCallback.onSessionStateChanged(mSession, state, err);
1720                 }
1721             });
1722         }
1723 
postBiInteractiveAppCreated(Uri biIAppUri, String biIAppId)1724         void postBiInteractiveAppCreated(Uri biIAppUri, String biIAppId) {
1725             mHandler.post(new Runnable() {
1726                 @Override
1727                 public void run() {
1728                     mSessionCallback.onBiInteractiveAppCreated(mSession, biIAppUri, biIAppId);
1729                 }
1730             });
1731         }
1732 
postTeletextAppStateChanged(int state)1733         void postTeletextAppStateChanged(int state) {
1734             mHandler.post(new Runnable() {
1735                 @Override
1736                 public void run() {
1737                     mSessionCallback.onTeletextAppStateChanged(mSession, state);
1738                 }
1739             });
1740         }
1741     }
1742 
1743     /**
1744      * Interface used to receive the created session.
1745      * @hide
1746      */
1747     public abstract static class SessionCallback {
1748         /**
1749          * This is called after {@link TvInteractiveAppManager#createSession} has been processed.
1750          *
1751          * @param session A {@link TvInteractiveAppManager.Session} instance created. This can be
1752          *                {@code null} if the creation request failed.
1753          */
onSessionCreated(@ullable Session session)1754         public void onSessionCreated(@Nullable Session session) {
1755         }
1756 
1757         /**
1758          * This is called when {@link TvInteractiveAppManager.Session} is released.
1759          * This typically happens when the process hosting the session has crashed or been killed.
1760          *
1761          * @param session the {@link TvInteractiveAppManager.Session} instance released.
1762          */
onSessionReleased(@onNull Session session)1763         public void onSessionReleased(@NonNull Session session) {
1764         }
1765 
1766         /**
1767          * This is called when {@link TvInteractiveAppService.Session#layoutSurface} is called to
1768          * change the layout of surface.
1769          *
1770          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1771          * @param left Left position.
1772          * @param top Top position.
1773          * @param right Right position.
1774          * @param bottom Bottom position.
1775          */
onLayoutSurface(Session session, int left, int top, int right, int bottom)1776         public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
1777         }
1778 
1779         /**
1780          * This is called when {@link TvInteractiveAppService.Session#requestCommand} is called.
1781          *
1782          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1783          * @param cmdType type of the command.
1784          * @param parameters parameters of the command.
1785          */
onCommandRequest( Session session, @TvInteractiveAppService.PlaybackCommandType String cmdType, Bundle parameters)1786         public void onCommandRequest(
1787                 Session session,
1788                 @TvInteractiveAppService.PlaybackCommandType String cmdType,
1789                 Bundle parameters) {
1790         }
1791 
1792         /**
1793          * This is called when {@link TvInteractiveAppService.Session#SetVideoBounds} is called.
1794          *
1795          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1796          */
onSetVideoBounds(Session session, Rect rect)1797         public void onSetVideoBounds(Session session, Rect rect) {
1798         }
1799 
1800         /**
1801          * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelUri} is
1802          * called.
1803          *
1804          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1805          */
onRequestCurrentChannelUri(Session session)1806         public void onRequestCurrentChannelUri(Session session) {
1807         }
1808 
1809         /**
1810          * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelLcn} is
1811          * called.
1812          *
1813          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1814          */
onRequestCurrentChannelLcn(Session session)1815         public void onRequestCurrentChannelLcn(Session session) {
1816         }
1817 
1818         /**
1819          * This is called when {@link TvInteractiveAppService.Session#RequestStreamVolume} is
1820          * called.
1821          *
1822          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1823          */
onRequestStreamVolume(Session session)1824         public void onRequestStreamVolume(Session session) {
1825         }
1826 
1827         /**
1828          * This is called when {@link TvInteractiveAppService.Session#RequestTrackInfoList} is
1829          * called.
1830          *
1831          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1832          */
onRequestTrackInfoList(Session session)1833         public void onRequestTrackInfoList(Session session) {
1834         }
1835 
1836         /**
1837          * This is called when {@link TvInteractiveAppService.Session#RequestCurrentTvInputId} is
1838          * called.
1839          *
1840          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
1841          */
onRequestCurrentTvInputId(Session session)1842         public void onRequestCurrentTvInputId(Session session) {
1843         }
1844 
1845         /**
1846          * This is called when
1847          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
1848          * called.
1849          *
1850          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
1851          * @param signingId the ID to identify the request.
1852          * @param algorithm the standard name of the signature algorithm requested, such as
1853          *                  MD5withRSA, SHA256withDSA, etc.
1854          * @param alias the alias of the corresponding {@link java.security.KeyStore}.
1855          * @param data the original bytes to be signed.
1856          */
onRequestSigning( Session session, String signingId, String algorithm, String alias, byte[] data)1857         public void onRequestSigning(
1858                 Session session, String signingId, String algorithm, String alias, byte[] data) {
1859         }
1860 
1861         /**
1862          * This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is
1863          * called.
1864          *
1865          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1866          * @param state the current state.
1867          */
onSessionStateChanged( Session session, @InteractiveAppState int state, @ErrorCode int err)1868         public void onSessionStateChanged(
1869                 Session session,
1870                 @InteractiveAppState int state,
1871                 @ErrorCode int err) {
1872         }
1873 
1874         /**
1875          * This is called when {@link TvInteractiveAppService.Session#notifyBiInteractiveAppCreated}
1876          * is called.
1877          *
1878          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1879          * @param biIAppUri URI associated this BI interactive app. This is the same URI in
1880          *                  {@link Session#createBiInteractiveApp(Uri, Bundle)}
1881          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
1882          *                 app.
1883          */
onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId)1884         public void onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId) {
1885         }
1886 
1887         /**
1888          * This is called when {@link TvInteractiveAppService.Session#notifyTeletextAppStateChanged}
1889          * is called.
1890          *
1891          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
1892          * @param state the current state.
1893          */
onTeletextAppStateChanged( Session session, @TvInteractiveAppManager.TeletextAppState int state)1894         public void onTeletextAppStateChanged(
1895                 Session session, @TvInteractiveAppManager.TeletextAppState int state) {
1896         }
1897     }
1898 }
1899