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