• 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.NonNull;
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.content.res.XmlResourceParser;
25 import android.graphics.PixelFormat;
26 import android.graphics.Rect;
27 import android.graphics.RectF;
28 import android.media.tv.TvInputManager;
29 import android.media.tv.TvTrackInfo;
30 import android.media.tv.TvView;
31 import android.media.tv.interactive.TvInteractiveAppManager.Session;
32 import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback;
33 import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.util.AttributeSet;
38 import android.util.Log;
39 import android.util.Xml;
40 import android.view.InputEvent;
41 import android.view.KeyEvent;
42 import android.view.Surface;
43 import android.view.SurfaceHolder;
44 import android.view.SurfaceView;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.view.ViewRootImpl;
48 
49 import java.security.KeyStore;
50 import java.util.List;
51 import java.util.concurrent.Executor;
52 
53 /**
54  * Displays contents of interactive TV applications.
55  */
56 public class TvInteractiveAppView extends ViewGroup {
57     private static final String TAG = "TvInteractiveAppView";
58     private static final boolean DEBUG = false;
59 
60     private static final int SET_TVVIEW_SUCCESS = 1;
61     private static final int SET_TVVIEW_FAIL = 2;
62     private static final int UNSET_TVVIEW_SUCCESS = 3;
63     private static final int UNSET_TVVIEW_FAIL = 4;
64 
65     /**
66      * Used to share client {@link java.security.cert.Certificate} with
67      * {@link TvInteractiveAppService}.
68      * @see #createBiInteractiveApp(Uri, Bundle)
69      * @see java.security.cert.Certificate
70      */
71     public static final String BI_INTERACTIVE_APP_KEY_CERTIFICATE = "certificate";
72     /**
73      * Used to share the {@link KeyStore} alias with {@link TvInteractiveAppService}.
74      * @see #createBiInteractiveApp(Uri, Bundle)
75      * @see KeyStore#aliases()
76      */
77     public static final String BI_INTERACTIVE_APP_KEY_ALIAS = "alias";
78     /**
79      * Used to share the {@link java.security.PrivateKey} with {@link TvInteractiveAppService}.
80      * <p>The private key is optional. It is used to encrypt data when necessary.
81      *
82      * @see #createBiInteractiveApp(Uri, Bundle)
83      * @see java.security.PrivateKey
84      */
85     public static final String BI_INTERACTIVE_APP_KEY_PRIVATE_KEY = "private_key";
86     /**
87      * Additional HTTP headers to be used by {@link TvInteractiveAppService} to load the
88      * broadcast-independent interactive application.
89      * @see #createBiInteractiveApp(Uri, Bundle)
90      */
91     public static final String BI_INTERACTIVE_APP_KEY_HTTP_ADDITIONAL_HEADERS =
92             "http_additional_headers";
93     /**
94      * HTTP user agent to be used by {@link TvInteractiveAppService} for broadcast-independent
95      * interactive application.
96      * @see #createBiInteractiveApp(Uri, Bundle)
97      */
98     public static final String BI_INTERACTIVE_APP_KEY_HTTP_USER_AGENT = "http_user_agent";
99 
100     /**
101      * The name of the method where the error happened, if applicable. For example, if there is an
102      * error during signing, the request name is "onRequestSigning".
103      * @see #notifyError(String, Bundle)
104      */
105     public static final String ERROR_KEY_METHOD_NAME = "method_name";
106 
107     private final TvInteractiveAppManager mTvInteractiveAppManager;
108     private final Handler mHandler = new Handler();
109     private final Object mCallbackLock = new Object();
110     private Session mSession;
111     private MySessionCallback mSessionCallback;
112     private TvInteractiveAppCallback mCallback;
113     private Executor mCallbackExecutor;
114     private SurfaceView mSurfaceView;
115     private Surface mSurface;
116 
117     private boolean mSurfaceChanged;
118     private int mSurfaceFormat;
119     private int mSurfaceWidth;
120     private int mSurfaceHeight;
121 
122     private boolean mUseRequestedSurfaceLayout;
123     private int mSurfaceViewLeft;
124     private int mSurfaceViewRight;
125     private int mSurfaceViewTop;
126     private int mSurfaceViewBottom;
127 
128     private boolean mMediaViewCreated;
129     private Rect mMediaViewFrame;
130 
131     private final AttributeSet mAttrs;
132     private final int mDefStyleAttr;
133     private final XmlResourceParser mParser;
134     private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
135 
136     private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
137         @Override
138         public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
139             if (DEBUG) {
140                 Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format
141                         + ", width=" + width + ", height=" + height + ")");
142             }
143             mSurfaceFormat = format;
144             mSurfaceWidth = width;
145             mSurfaceHeight = height;
146             mSurfaceChanged = true;
147             dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
148         }
149 
150         @Override
151         public void surfaceCreated(SurfaceHolder holder) {
152             mSurface = holder.getSurface();
153             setSessionSurface(mSurface);
154         }
155 
156         @Override
157         public void surfaceDestroyed(SurfaceHolder holder) {
158             mSurface = null;
159             mSurfaceChanged = false;
160             setSessionSurface(null);
161         }
162     };
163 
TvInteractiveAppView(@onNull Context context)164     public TvInteractiveAppView(@NonNull Context context) {
165         this(context, null, 0);
166     }
167 
TvInteractiveAppView(@onNull Context context, @Nullable AttributeSet attrs)168     public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs) {
169         this(context, attrs, 0);
170     }
171 
TvInteractiveAppView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)172     public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs,
173             int defStyleAttr) {
174         super(context, attrs, defStyleAttr);
175         int sourceResId = Resources.getAttributeSetSourceResId(attrs);
176         if (sourceResId != Resources.ID_NULL) {
177             Log.d(TAG, "Build local AttributeSet");
178             mParser  = context.getResources().getXml(sourceResId);
179             mAttrs = Xml.asAttributeSet(mParser);
180         } else {
181             Log.d(TAG, "Use passed in AttributeSet");
182             mParser = null;
183             mAttrs = attrs;
184         }
185         mDefStyleAttr = defStyleAttr;
186         resetSurfaceView();
187         mTvInteractiveAppManager = (TvInteractiveAppManager) getContext().getSystemService(
188                 Context.TV_INTERACTIVE_APP_SERVICE);
189     }
190 
191     /**
192      * Sets the callback to be invoked when an event is dispatched to this TvInteractiveAppView.
193      *
194      * @param callback the callback to receive events. MUST NOT be {@code null}.
195      *
196      * @see #clearCallback()
197      */
setCallback( @onNull @allbackExecutor Executor executor, @NonNull TvInteractiveAppCallback callback)198     public void setCallback(
199             @NonNull @CallbackExecutor Executor executor,
200             @NonNull TvInteractiveAppCallback callback) {
201         com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, callback);
202         synchronized (mCallbackLock) {
203             mCallbackExecutor = executor;
204             mCallback = callback;
205         }
206     }
207 
208     /**
209      * Clears the callback.
210      *
211      * @see #setCallback(Executor, TvInteractiveAppCallback)
212      */
clearCallback()213     public void clearCallback() {
214         synchronized (mCallbackLock) {
215             mCallback = null;
216             mCallbackExecutor = null;
217         }
218     }
219 
220     @Override
onAttachedToWindow()221     public void onAttachedToWindow() {
222         super.onAttachedToWindow();
223         createSessionMediaView();
224     }
225 
226     @Override
onDetachedFromWindow()227     public void onDetachedFromWindow() {
228         removeSessionMediaView();
229         super.onDetachedFromWindow();
230     }
231 
232     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)233     public void onLayout(boolean changed, int left, int top, int right, int bottom) {
234         if (DEBUG) {
235             Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
236                     + ", bottom=" + bottom + ",)");
237         }
238         if (mUseRequestedSurfaceLayout) {
239             mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
240                     mSurfaceViewBottom);
241         } else {
242             mSurfaceView.layout(0, 0, right - left, bottom - top);
243         }
244     }
245 
246     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)247     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
248         mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
249         int width = mSurfaceView.getMeasuredWidth();
250         int height = mSurfaceView.getMeasuredHeight();
251         int childState = mSurfaceView.getMeasuredState();
252         setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
253                 resolveSizeAndState(height, heightMeasureSpec,
254                         childState << MEASURED_HEIGHT_STATE_SHIFT));
255     }
256 
257     @Override
onVisibilityChanged(@onNull View changedView, int visibility)258     public void onVisibilityChanged(@NonNull View changedView, int visibility) {
259         super.onVisibilityChanged(changedView, visibility);
260         mSurfaceView.setVisibility(visibility);
261         if (visibility == View.VISIBLE) {
262             createSessionMediaView();
263         } else {
264             removeSessionMediaView();
265         }
266     }
267 
resetSurfaceView()268     private void resetSurfaceView() {
269         if (mSurfaceView != null) {
270             mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
271             removeView(mSurfaceView);
272         }
273         mSurface = null;
274         mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
275             @Override
276             protected void updateSurface() {
277                 super.updateSurface();
278                 relayoutSessionMediaView();
279             }};
280         // The surface view's content should be treated as secure all the time.
281         mSurfaceView.setSecure(true);
282         mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
283         mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
284         addView(mSurfaceView);
285     }
286 
287     /**
288      * Resets this TvInteractiveAppView to release its resources.
289      *
290      * <p>It can be reused by call {@link #prepareInteractiveApp(String, int)}.
291      */
reset()292     public void reset() {
293         if (DEBUG) Log.d(TAG, "reset()");
294         resetInternal();
295     }
296 
createSessionMediaView()297     private void createSessionMediaView() {
298         // TODO: handle z-order
299         if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) {
300             return;
301         }
302         mMediaViewFrame = getViewFrameOnScreen();
303         mSession.createMediaView(this, mMediaViewFrame);
304         mMediaViewCreated = true;
305     }
306 
removeSessionMediaView()307     private void removeSessionMediaView() {
308         if (mSession == null || !mMediaViewCreated) {
309             return;
310         }
311         mSession.removeMediaView();
312         mMediaViewCreated = false;
313         mMediaViewFrame = null;
314     }
315 
relayoutSessionMediaView()316     private void relayoutSessionMediaView() {
317         if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) {
318             return;
319         }
320         Rect viewFrame = getViewFrameOnScreen();
321         if (viewFrame.equals(mMediaViewFrame)) {
322             return;
323         }
324         mSession.relayoutMediaView(viewFrame);
325         mMediaViewFrame = viewFrame;
326     }
327 
getViewFrameOnScreen()328     private Rect getViewFrameOnScreen() {
329         Rect frame = new Rect();
330         getGlobalVisibleRect(frame);
331         RectF frameF = new RectF(frame);
332         getMatrix().mapRect(frameF);
333         frameF.round(frame);
334         return frame;
335     }
336 
setSessionSurface(Surface surface)337     private void setSessionSurface(Surface surface) {
338         if (mSession == null) {
339             return;
340         }
341         mSession.setSurface(surface);
342     }
343 
dispatchSurfaceChanged(int format, int width, int height)344     private void dispatchSurfaceChanged(int format, int width, int height) {
345         if (mSession == null) {
346             return;
347         }
348         mSession.dispatchSurfaceChanged(format, width, height);
349     }
350 
351     private final FinishedInputEventCallback mFinishedInputEventCallback =
352             new FinishedInputEventCallback() {
353                 @Override
354                 public void onFinishedInputEvent(Object token, boolean handled) {
355                     if (DEBUG) {
356                         Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled="
357                                 + handled + ")");
358                     }
359                     if (handled) {
360                         return;
361                     }
362                     // TODO: Re-order unhandled events.
363                     InputEvent event = (InputEvent) token;
364                     if (dispatchUnhandledInputEvent(event)) {
365                         return;
366                     }
367                     ViewRootImpl viewRootImpl = getViewRootImpl();
368                     if (viewRootImpl != null) {
369                         viewRootImpl.dispatchUnhandledInputEvent(event);
370                     }
371                 }
372             };
373 
374     /**
375      * Dispatches an unhandled input event to the next receiver.
376      *
377      * It gives the host application a chance to dispatch the unhandled input events.
378      *
379      * @param event The input event.
380      * @return {@code true} if the event was handled by the view, {@code false} otherwise.
381      */
dispatchUnhandledInputEvent(@onNull InputEvent event)382     public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
383         if (mOnUnhandledInputEventListener != null) {
384             if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
385                 return true;
386             }
387         }
388         return onUnhandledInputEvent(event);
389     }
390 
391     /**
392      * Called when an unhandled input event also has not been handled by the user provided
393      * callback. This is the last chance to handle the unhandled input event in the
394      * TvInteractiveAppView.
395      *
396      * @param event The input event.
397      * @return If you handled the event, return {@code true}. If you want to allow the event to be
398      *         handled by the next receiver, return {@code false}.
399      */
onUnhandledInputEvent(@onNull InputEvent event)400     public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
401         return false;
402     }
403 
404     /**
405      * Sets a listener to be invoked when an input event is not handled
406      * by the TV Interactive App.
407      *
408      * @param listener The callback to be invoked when the unhandled input event is received.
409      */
setOnUnhandledInputEventListener( @onNull @allbackExecutor Executor executor, @NonNull OnUnhandledInputEventListener listener)410     public void setOnUnhandledInputEventListener(
411             @NonNull @CallbackExecutor Executor executor,
412             @NonNull OnUnhandledInputEventListener listener) {
413         mOnUnhandledInputEventListener = listener;
414         // TODO: handle CallbackExecutor
415     }
416 
417     /**
418      * Gets the {@link OnUnhandledInputEventListener}.
419      * <p>Returns {@code null} if the listener is not set or is cleared.
420      *
421      * @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener)
422      * @see #clearOnUnhandledInputEventListener()
423      */
424     @Nullable
getOnUnhandledInputEventListener()425     public OnUnhandledInputEventListener getOnUnhandledInputEventListener() {
426         return mOnUnhandledInputEventListener;
427     }
428 
429     /**
430      * Clears the {@link OnUnhandledInputEventListener}.
431      */
clearOnUnhandledInputEventListener()432     public void clearOnUnhandledInputEventListener() {
433         mOnUnhandledInputEventListener = null;
434     }
435 
436     @Override
dispatchKeyEvent(@onNull KeyEvent event)437     public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
438         if (super.dispatchKeyEvent(event)) {
439             return true;
440         }
441         if (mSession == null) {
442             return false;
443         }
444         InputEvent copiedEvent = event.copy();
445         int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
446                 mHandler);
447         return ret != Session.DISPATCH_NOT_HANDLED;
448     }
449 
450     /**
451      * Prepares the interactive application runtime environment of corresponding
452      * {@link TvInteractiveAppService}.
453      *
454      * @param iAppServiceId the interactive app service ID, which can be found in
455      *                      {@link TvInteractiveAppServiceInfo#getId()}.
456      *
457      * @see android.media.tv.interactive.TvInteractiveAppManager#getTvInteractiveAppServiceList()
458      */
prepareInteractiveApp( @onNull String iAppServiceId, @TvInteractiveAppServiceInfo.InteractiveAppType int type)459     public void prepareInteractiveApp(
460             @NonNull String iAppServiceId,
461             @TvInteractiveAppServiceInfo.InteractiveAppType int type) {
462         // TODO: document and handle the cases that this method is called multiple times.
463         if (DEBUG) {
464             Log.d(TAG, "prepareInteractiveApp");
465         }
466         mSessionCallback = new MySessionCallback(iAppServiceId, type);
467         if (mTvInteractiveAppManager != null) {
468             mTvInteractiveAppManager.createSession(iAppServiceId, type, mSessionCallback, mHandler);
469         }
470     }
471 
472     /**
473      * Starts the interactive application.
474      */
startInteractiveApp()475     public void startInteractiveApp() {
476         if (DEBUG) {
477             Log.d(TAG, "startInteractiveApp");
478         }
479         if (mSession != null) {
480             mSession.startInteractiveApp();
481         }
482     }
483 
484     /**
485      * Stops the interactive application.
486      */
stopInteractiveApp()487     public void stopInteractiveApp() {
488         if (DEBUG) {
489             Log.d(TAG, "stopInteractiveApp");
490         }
491         if (mSession != null) {
492             mSession.stopInteractiveApp();
493         }
494     }
495 
496     /**
497      * Resets the interactive application.
498      *
499      * <p>This releases the resources of the corresponding {@link TvInteractiveAppService.Session}.
500      */
resetInteractiveApp()501     public void resetInteractiveApp() {
502         if (DEBUG) {
503             Log.d(TAG, "resetInteractiveApp");
504         }
505         if (mSession != null) {
506             mSession.resetInteractiveApp();
507         }
508     }
509 
510     /**
511      * Sends current channel URI to related TV interactive app.
512      *
513      * @param channelUri The current channel URI; {@code null} if there is no currently tuned
514      *                   channel.
515      */
sendCurrentChannelUri(@ullable Uri channelUri)516     public void sendCurrentChannelUri(@Nullable Uri channelUri) {
517         if (DEBUG) {
518             Log.d(TAG, "sendCurrentChannelUri");
519         }
520         if (mSession != null) {
521             mSession.sendCurrentChannelUri(channelUri);
522         }
523     }
524 
525     /**
526      * Sends current channel logical channel number (LCN) to related TV interactive app.
527      */
sendCurrentChannelLcn(int lcn)528     public void sendCurrentChannelLcn(int lcn) {
529         if (DEBUG) {
530             Log.d(TAG, "sendCurrentChannelLcn");
531         }
532         if (mSession != null) {
533             mSession.sendCurrentChannelLcn(lcn);
534         }
535     }
536 
537     /**
538      * Sends stream volume to related TV interactive app.
539      *
540      * @param volume a volume value between {@code 0.0f} and {@code 1.0f}, inclusive.
541      */
sendStreamVolume(float volume)542     public void sendStreamVolume(float volume) {
543         if (DEBUG) {
544             Log.d(TAG, "sendStreamVolume");
545         }
546         if (mSession != null) {
547             mSession.sendStreamVolume(volume);
548         }
549     }
550 
551     /**
552      * Sends track info list to related TV interactive app.
553      */
sendTrackInfoList(@ullable List<TvTrackInfo> tracks)554     public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) {
555         if (DEBUG) {
556             Log.d(TAG, "sendTrackInfoList");
557         }
558         if (mSession != null) {
559             mSession.sendTrackInfoList(tracks);
560         }
561     }
562 
563     /**
564      * Sends current TV input ID to related TV interactive app.
565      *
566      * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
567      *                tuned.
568      * @see android.media.tv.TvInputInfo
569      */
sendCurrentTvInputId(@ullable String inputId)570     public void sendCurrentTvInputId(@Nullable String inputId) {
571         if (DEBUG) {
572             Log.d(TAG, "sendCurrentTvInputId");
573         }
574         if (mSession != null) {
575             mSession.sendCurrentTvInputId(inputId);
576         }
577     }
578 
579     /**
580      * Sends signing result to related TV interactive app.
581      *
582      * <p>This is used when the corresponding server of the broadcast-independent interactive
583      * app requires signing during handshaking, and the interactive app service doesn't have
584      * the built-in private key. The private key is provided by the content providers and
585      * pre-built in the related app, such as TV app.
586      *
587      * @param signingId the ID to identify the request. It's the same as the corresponding ID in
588      *        {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])}
589      * @param result the signed result.
590      */
sendSigningResult(@onNull String signingId, @NonNull byte[] result)591     public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
592         if (DEBUG) {
593             Log.d(TAG, "sendSigningResult");
594         }
595         if (mSession != null) {
596             mSession.sendSigningResult(signingId, result);
597         }
598     }
599 
600     /**
601      * Notifies the corresponding {@link TvInteractiveAppService} when there is an error.
602      *
603      * @param errMsg the message of the error.
604      * @param params additional parameters of the error. For example, the signingId of {@link
605      *     TvInteractiveAppCallback#onRequestSigning(String, String, String, String, byte[])} can be
606      *     included to identify the related signing request, and the method name "onRequestSigning"
607      *     can also be added to the params.
608      *
609      * @see #ERROR_KEY_METHOD_NAME
610      */
notifyError(@onNull String errMsg, @NonNull Bundle params)611     public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
612         if (DEBUG) {
613             Log.d(TAG, "notifyError msg=" + errMsg + "; params=" + params);
614         }
615         if (mSession != null) {
616             mSession.notifyError(errMsg, params);
617         }
618     }
619 
resetInternal()620     private void resetInternal() {
621         mSessionCallback = null;
622         if (mSession != null) {
623             setSessionSurface(null);
624             removeSessionMediaView();
625             mUseRequestedSurfaceLayout = false;
626             mSession.release();
627             mSession = null;
628             resetSurfaceView();
629         }
630     }
631 
632     /**
633      * Creates broadcast-independent(BI) interactive application.
634      *
635      * <p>{@link TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String)} will be
636      * called for the result.
637      *
638      * @param biIAppUri URI associated this BI interactive app.
639      * @param params optional parameters for broadcast-independent interactive application, such as
640      *               {@link #BI_INTERACTIVE_APP_KEY_CERTIFICATE}.
641      *
642      * @see TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String)
643      * @see #BI_INTERACTIVE_APP_KEY_CERTIFICATE
644      * @see #BI_INTERACTIVE_APP_KEY_HTTP_ADDITIONAL_HEADERS
645      * @see #BI_INTERACTIVE_APP_KEY_HTTP_USER_AGENT
646      */
createBiInteractiveApp(@onNull Uri biIAppUri, @Nullable Bundle params)647     public void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
648         if (DEBUG) {
649             Log.d(TAG, "createBiInteractiveApp Uri=" + biIAppUri + ", params=" + params);
650         }
651         if (mSession != null) {
652             mSession.createBiInteractiveApp(biIAppUri, params);
653         }
654     }
655 
656     /**
657      * Destroys broadcast-independent(BI) interactive application.
658      *
659      * @param biIAppId the BI interactive app ID from {@link #createBiInteractiveApp(Uri, Bundle)}
660      *
661      * @see #createBiInteractiveApp(Uri, Bundle)
662      */
destroyBiInteractiveApp(@onNull String biIAppId)663     public void destroyBiInteractiveApp(@NonNull String biIAppId) {
664         if (DEBUG) {
665             Log.d(TAG, "destroyBiInteractiveApp biIAppId=" + biIAppId);
666         }
667         if (mSession != null) {
668             mSession.destroyBiInteractiveApp(biIAppId);
669         }
670     }
671 
672     /** @hide */
getInteractiveAppSession()673     public Session getInteractiveAppSession() {
674         return mSession;
675     }
676 
677     /**
678      * Sets the TvInteractiveAppView to receive events from TIS. This method links the session of
679      * TvInteractiveAppManager to TvInputManager session, so the TIAS can get the TIS events.
680      *
681      * @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions.
682      * @return The result of the operation.
683      */
setTvView(@ullable TvView tvView)684     public int setTvView(@Nullable TvView tvView) {
685         if (tvView == null) {
686             return unsetTvView();
687         }
688         TvInputManager.Session inputSession = tvView.getInputSession();
689         if (inputSession == null || mSession == null) {
690             return SET_TVVIEW_FAIL;
691         }
692         mSession.setInputSession(inputSession);
693         inputSession.setInteractiveAppSession(mSession);
694         return SET_TVVIEW_SUCCESS;
695     }
696 
unsetTvView()697     private int unsetTvView() {
698         if (mSession == null || mSession.getInputSession() == null) {
699             return UNSET_TVVIEW_FAIL;
700         }
701         mSession.getInputSession().setInteractiveAppSession(null);
702         mSession.setInputSession(null);
703         return UNSET_TVVIEW_SUCCESS;
704     }
705 
706     /**
707      * To toggle Digital Teletext Application if there is one in AIT app list.
708      *
709      * <p>A Teletext Application is a broadcast-related application to display text and basic
710      * graphics.
711      *
712      * @param enable {@code true} to enable Teletext app; {@code false} to disable it.
713      */
setTeletextAppEnabled(boolean enable)714     public void setTeletextAppEnabled(boolean enable) {
715         if (DEBUG) {
716             Log.d(TAG, "setTeletextAppEnabled enable=" + enable);
717         }
718         if (mSession != null) {
719             mSession.setTeletextAppEnabled(enable);
720         }
721     }
722 
723     /**
724      * Callback used to receive various status updates on the {@link TvInteractiveAppView}.
725      */
726     public abstract static class TvInteractiveAppCallback {
727         // TODO: unhide the following public APIs
728 
729         /**
730          * This is called when a playback command is requested to be processed by the related TV
731          * input.
732          *
733          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
734          * @param cmdType type of the command
735          * @param parameters parameters of the command
736          */
onPlaybackCommandRequest( @onNull String iAppServiceId, @NonNull @TvInteractiveAppService.PlaybackCommandType String cmdType, @NonNull Bundle parameters)737         public void onPlaybackCommandRequest(
738                 @NonNull String iAppServiceId,
739                 @NonNull @TvInteractiveAppService.PlaybackCommandType String cmdType,
740                 @NonNull Bundle parameters) {
741         }
742 
743         /**
744          * This is called when the state of corresponding interactive app is changed.
745          *
746          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
747          * @param state the current state.
748          * @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE}
749          *              is used when the state is not
750          *              {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}.
751          */
onStateChanged( @onNull String iAppServiceId, @TvInteractiveAppManager.InteractiveAppState int state, @TvInteractiveAppManager.ErrorCode int err)752         public void onStateChanged(
753                 @NonNull String iAppServiceId,
754                 @TvInteractiveAppManager.InteractiveAppState int state,
755                 @TvInteractiveAppManager.ErrorCode int err) {
756         }
757 
758         /**
759          * This is called when broadcast-independent (BI) interactive app is created.
760          *
761          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
762          * @param biIAppUri URI associated this BI interactive app. This is the same URI in
763          *                  {@link #createBiInteractiveApp(Uri, Bundle)}
764          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
765          *                 app. {@code null} if it's not created successfully.
766          *
767          * @see #createBiInteractiveApp(Uri, Bundle)
768          * @see #destroyBiInteractiveApp(String)
769          */
onBiInteractiveAppCreated(@onNull String iAppServiceId, @NonNull Uri biIAppUri, @Nullable String biIAppId)770         public void onBiInteractiveAppCreated(@NonNull String iAppServiceId, @NonNull Uri biIAppUri,
771                 @Nullable String biIAppId) {
772         }
773 
774         /**
775          * This is called when the digital teletext app state is changed.
776          *
777          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
778          * @param state digital teletext app current state.
779          */
onTeletextAppStateChanged( @onNull String iAppServiceId, @TvInteractiveAppManager.TeletextAppState int state)780         public void onTeletextAppStateChanged(
781                 @NonNull String iAppServiceId,
782                 @TvInteractiveAppManager.TeletextAppState int state) {
783         }
784 
785         /**
786          * This is called when {@link TvInteractiveAppService.Session#setVideoBounds(Rect)} is
787          * called.
788          *
789          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
790          */
onSetVideoBounds(@onNull String iAppServiceId, @NonNull Rect rect)791         public void onSetVideoBounds(@NonNull String iAppServiceId, @NonNull Rect rect) {
792         }
793 
794         /**
795          * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelUri()} is
796          * called.
797          *
798          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
799          */
onRequestCurrentChannelUri(@onNull String iAppServiceId)800         public void onRequestCurrentChannelUri(@NonNull String iAppServiceId) {
801         }
802 
803         /**
804          * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelLcn()} is
805          * called.
806          *
807          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
808          */
onRequestCurrentChannelLcn(@onNull String iAppServiceId)809         public void onRequestCurrentChannelLcn(@NonNull String iAppServiceId) {
810         }
811 
812         /**
813          * This is called when {@link TvInteractiveAppService.Session#requestStreamVolume()} is
814          * called.
815          *
816          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
817          */
onRequestStreamVolume(@onNull String iAppServiceId)818         public void onRequestStreamVolume(@NonNull String iAppServiceId) {
819         }
820 
821         /**
822          * This is called when {@link TvInteractiveAppService.Session#requestTrackInfoList()} is
823          * called.
824          *
825          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
826          */
onRequestTrackInfoList(@onNull String iAppServiceId)827         public void onRequestTrackInfoList(@NonNull String iAppServiceId) {
828         }
829 
830         /**
831          * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId()} is
832          * called.
833          *
834          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
835          */
onRequestCurrentTvInputId(@onNull String iAppServiceId)836         public void onRequestCurrentTvInputId(@NonNull String iAppServiceId) {
837         }
838 
839         /**
840          * This is called when
841          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
842          * called.
843          *
844          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
845          * @param signingId the ID to identify the request.
846          * @param algorithm the standard name of the signature algorithm requested, such as
847          *                  MD5withRSA, SHA256withDSA, etc.
848          * @param alias the alias of the corresponding {@link java.security.KeyStore}.
849          * @param data the original bytes to be signed.
850          */
onRequestSigning(@onNull String iAppServiceId, @NonNull String signingId, @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data)851         public void onRequestSigning(@NonNull String iAppServiceId, @NonNull String signingId,
852                 @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data) {
853         }
854 
855     }
856 
857     /**
858      * Interface definition for a callback to be invoked when the unhandled input event is received.
859      */
860     public interface OnUnhandledInputEventListener {
861         /**
862          * Called when an input event was not handled by the TV Interactive App.
863          *
864          * <p>This is called asynchronously from where the event is dispatched. It gives the host
865          * application a chance to handle the unhandled input events.
866          *
867          * @param event The input event.
868          * @return If you handled the event, return {@code true}. If you want to allow the event to
869          *         be handled by the next receiver, return {@code false}.
870          */
onUnhandledInputEvent(@onNull InputEvent event)871         boolean onUnhandledInputEvent(@NonNull InputEvent event);
872     }
873 
874     private class MySessionCallback extends SessionCallback {
875         final String mIAppServiceId;
876         int mType;
877 
MySessionCallback(String iAppServiceId, int type)878         MySessionCallback(String iAppServiceId, int type) {
879             mIAppServiceId = iAppServiceId;
880             mType = type;
881         }
882 
883         @Override
onSessionCreated(Session session)884         public void onSessionCreated(Session session) {
885             if (DEBUG) {
886                 Log.d(TAG, "onSessionCreated()");
887             }
888             if (this != mSessionCallback) {
889                 Log.w(TAG, "onSessionCreated - session already created");
890                 // This callback is obsolete.
891                 if (session != null) {
892                     session.release();
893                 }
894                 return;
895             }
896             mSession = session;
897             if (session != null) {
898                 // mSurface may not be ready yet as soon as starting an application.
899                 // In the case, we don't send Session.setSurface(null) unnecessarily.
900                 // setSessionSurface will be called in surfaceCreated.
901                 if (mSurface != null) {
902                     setSessionSurface(mSurface);
903                     if (mSurfaceChanged) {
904                         dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
905                     }
906                 }
907                 createSessionMediaView();
908             } else {
909                 // Failed to create
910                 // Todo: forward error to Tv App
911                 mSessionCallback = null;
912             }
913         }
914 
915         @Override
onSessionReleased(Session session)916         public void onSessionReleased(Session session) {
917             if (DEBUG) {
918                 Log.d(TAG, "onSessionReleased()");
919             }
920             if (this != mSessionCallback) {
921                 Log.w(TAG, "onSessionReleased - session not created");
922                 return;
923             }
924             mMediaViewCreated = false;
925             mMediaViewFrame = null;
926             mSessionCallback = null;
927             mSession = null;
928         }
929 
930         @Override
onLayoutSurface(Session session, int left, int top, int right, int bottom)931         public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
932             if (DEBUG) {
933                 Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
934                         + right + ", bottom=" + bottom + ",)");
935             }
936             if (this != mSessionCallback) {
937                 Log.w(TAG, "onLayoutSurface - session not created");
938                 return;
939             }
940             mSurfaceViewLeft = left;
941             mSurfaceViewTop = top;
942             mSurfaceViewRight = right;
943             mSurfaceViewBottom = bottom;
944             mUseRequestedSurfaceLayout = true;
945             requestLayout();
946         }
947 
948         @Override
onCommandRequest( Session session, @TvInteractiveAppService.PlaybackCommandType String cmdType, Bundle parameters)949         public void onCommandRequest(
950                 Session session,
951                 @TvInteractiveAppService.PlaybackCommandType String cmdType,
952                 Bundle parameters) {
953             if (DEBUG) {
954                 Log.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters="
955                         + parameters.toString() + ")");
956             }
957             if (this != mSessionCallback) {
958                 Log.w(TAG, "onCommandRequest - session not created");
959                 return;
960             }
961             synchronized (mCallbackLock) {
962                 if (mCallbackExecutor != null) {
963                     mCallbackExecutor.execute(() -> {
964                         synchronized (mCallbackLock) {
965                             if (mCallback != null) {
966                                 mCallback.onPlaybackCommandRequest(
967                                         mIAppServiceId, cmdType, parameters);
968                             }
969                         }
970                     });
971                 }
972             }
973         }
974 
975         @Override
onSessionStateChanged( Session session, @TvInteractiveAppManager.InteractiveAppState int state, @TvInteractiveAppManager.ErrorCode int err)976         public void onSessionStateChanged(
977                 Session session,
978                 @TvInteractiveAppManager.InteractiveAppState int state,
979                 @TvInteractiveAppManager.ErrorCode int err) {
980             if (DEBUG) {
981                 Log.d(TAG, "onSessionStateChanged (state=" + state + "; err=" + err + ")");
982             }
983             if (this != mSessionCallback) {
984                 Log.w(TAG, "onSessionStateChanged - session not created");
985                 return;
986             }
987             synchronized (mCallbackLock) {
988                 if (mCallbackExecutor != null) {
989                     mCallbackExecutor.execute(() -> {
990                         synchronized (mCallbackLock) {
991                             if (mCallback != null) {
992                                 mCallback.onStateChanged(mIAppServiceId, state, err);
993                             }
994                         }
995                     });
996                 }
997             }
998         }
999 
1000         @Override
onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId)1001         public void onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId) {
1002             if (DEBUG) {
1003                 Log.d(TAG, "onBiInteractiveAppCreated (biIAppUri=" + biIAppUri + ", biIAppId="
1004                         + biIAppId + ")");
1005             }
1006             if (this != mSessionCallback) {
1007                 Log.w(TAG, "onBiInteractiveAppCreated - session not created");
1008                 return;
1009             }
1010             synchronized (mCallbackLock) {
1011                 if (mCallbackExecutor != null) {
1012                     mCallbackExecutor.execute(() -> {
1013                         synchronized (mCallbackLock) {
1014                             if (mCallback != null) {
1015                                 mCallback.onBiInteractiveAppCreated(
1016                                         mIAppServiceId, biIAppUri, biIAppId);
1017                             }
1018                         }
1019                     });
1020                 }
1021             }
1022         }
1023 
1024         @Override
onTeletextAppStateChanged(Session session, int state)1025         public void onTeletextAppStateChanged(Session session, int state) {
1026             if (DEBUG) {
1027                 Log.d(TAG, "onTeletextAppStateChanged (state=" + state +  ")");
1028             }
1029             if (this != mSessionCallback) {
1030                 Log.w(TAG, "onTeletextAppStateChanged - session not created");
1031                 return;
1032             }
1033             if (mCallback != null) {
1034                 mCallback.onTeletextAppStateChanged(mIAppServiceId, state);
1035             }
1036         }
1037 
1038         @Override
onSetVideoBounds(Session session, Rect rect)1039         public void onSetVideoBounds(Session session, Rect rect) {
1040             if (DEBUG) {
1041                 Log.d(TAG, "onSetVideoBounds (rect=" + rect + ")");
1042             }
1043             if (this != mSessionCallback) {
1044                 Log.w(TAG, "onSetVideoBounds - session not created");
1045                 return;
1046             }
1047             synchronized (mCallbackLock) {
1048                 if (mCallbackExecutor != null) {
1049                     mCallbackExecutor.execute(() -> {
1050                         synchronized (mCallbackLock) {
1051                             if (mCallback != null) {
1052                                 mCallback.onSetVideoBounds(mIAppServiceId, rect);
1053                             }
1054                         }
1055                     });
1056                 }
1057             }
1058         }
1059 
1060         @Override
onRequestCurrentChannelUri(Session session)1061         public void onRequestCurrentChannelUri(Session session) {
1062             if (DEBUG) {
1063                 Log.d(TAG, "onRequestCurrentChannelUri");
1064             }
1065             if (this != mSessionCallback) {
1066                 Log.w(TAG, "onRequestCurrentChannelUri - session not created");
1067                 return;
1068             }
1069             synchronized (mCallbackLock) {
1070                 if (mCallbackExecutor != null) {
1071                     mCallbackExecutor.execute(() -> {
1072                         synchronized (mCallbackLock) {
1073                             if (mCallback != null) {
1074                                 mCallback.onRequestCurrentChannelUri(mIAppServiceId);
1075                             }
1076                         }
1077                     });
1078                 }
1079             }
1080         }
1081 
1082         @Override
onRequestCurrentChannelLcn(Session session)1083         public void onRequestCurrentChannelLcn(Session session) {
1084             if (DEBUG) {
1085                 Log.d(TAG, "onRequestCurrentChannelLcn");
1086             }
1087             if (this != mSessionCallback) {
1088                 Log.w(TAG, "onRequestCurrentChannelLcn - session not created");
1089                 return;
1090             }
1091             synchronized (mCallbackLock) {
1092                 if (mCallbackExecutor != null) {
1093                     mCallbackExecutor.execute(() -> {
1094                         synchronized (mCallbackLock) {
1095                             if (mCallback != null) {
1096                                 mCallback.onRequestCurrentChannelLcn(mIAppServiceId);
1097                             }
1098                         }
1099                     });
1100                 }
1101             }
1102         }
1103 
1104         @Override
onRequestStreamVolume(Session session)1105         public void onRequestStreamVolume(Session session) {
1106             if (DEBUG) {
1107                 Log.d(TAG, "onRequestStreamVolume");
1108             }
1109             if (this != mSessionCallback) {
1110                 Log.w(TAG, "onRequestStreamVolume - session not created");
1111                 return;
1112             }
1113             synchronized (mCallbackLock) {
1114                 if (mCallbackExecutor != null) {
1115                     mCallbackExecutor.execute(() -> {
1116                         synchronized (mCallbackLock) {
1117                             if (mCallback != null) {
1118                                 mCallback.onRequestStreamVolume(mIAppServiceId);
1119                             }
1120                         }
1121                     });
1122                 }
1123             }
1124         }
1125 
1126         @Override
onRequestTrackInfoList(Session session)1127         public void onRequestTrackInfoList(Session session) {
1128             if (DEBUG) {
1129                 Log.d(TAG, "onRequestTrackInfoList");
1130             }
1131             if (this != mSessionCallback) {
1132                 Log.w(TAG, "onRequestTrackInfoList - session not created");
1133                 return;
1134             }
1135             synchronized (mCallbackLock) {
1136                 if (mCallbackExecutor != null) {
1137                     mCallbackExecutor.execute(() -> {
1138                         synchronized (mCallbackLock) {
1139                             if (mCallback != null) {
1140                                 mCallback.onRequestTrackInfoList(mIAppServiceId);
1141                             }
1142                         }
1143                     });
1144                 }
1145             }
1146         }
1147 
1148         @Override
onRequestCurrentTvInputId(Session session)1149         public void onRequestCurrentTvInputId(Session session) {
1150             if (DEBUG) {
1151                 Log.d(TAG, "onRequestCurrentTvInputId");
1152             }
1153             if (this != mSessionCallback) {
1154                 Log.w(TAG, "onRequestCurrentTvInputId - session not created");
1155                 return;
1156             }
1157             if (mCallback != null) {
1158                 mCallback.onRequestCurrentTvInputId(mIAppServiceId);
1159             }
1160         }
1161 
1162         @Override
onRequestSigning( Session session, String id, String algorithm, String alias, byte[] data)1163         public void onRequestSigning(
1164                 Session session, String id, String algorithm, String alias, byte[] data) {
1165             if (DEBUG) {
1166                 Log.d(TAG, "onRequestSigning");
1167             }
1168             if (this != mSessionCallback) {
1169                 Log.w(TAG, "onRequestSigning - session not created");
1170                 return;
1171             }
1172             if (mCallback != null) {
1173                 mCallback.onRequestSigning(mIAppServiceId, id, algorithm, alias, data);
1174             }
1175         }
1176     }
1177 }
1178