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