1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.tv.interactive; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.SystemService; 24 import android.content.Context; 25 import android.graphics.Rect; 26 import android.media.PlaybackParams; 27 import android.media.tv.AdBuffer; 28 import android.media.tv.AdRequest; 29 import android.media.tv.AdResponse; 30 import android.media.tv.BroadcastInfoRequest; 31 import android.media.tv.BroadcastInfoResponse; 32 import android.media.tv.TvContentRating; 33 import android.media.tv.TvInputManager; 34 import android.media.tv.TvRecordingInfo; 35 import android.media.tv.TvTrackInfo; 36 import android.media.tv.interactive.TvInteractiveAppService.Session; 37 import android.net.Uri; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.IBinder; 41 import android.os.Looper; 42 import android.os.Message; 43 import android.os.RemoteException; 44 import android.util.Log; 45 import android.util.Pools; 46 import android.util.SparseArray; 47 import android.view.InputChannel; 48 import android.view.InputEvent; 49 import android.view.InputEventSender; 50 import android.view.Surface; 51 import android.view.View; 52 53 import com.android.internal.util.Preconditions; 54 55 import java.lang.annotation.Retention; 56 import java.lang.annotation.RetentionPolicy; 57 import java.util.ArrayList; 58 import java.util.Iterator; 59 import java.util.List; 60 import java.util.concurrent.Executor; 61 62 /** 63 * Central system API to the overall TV interactive application framework (TIAF) architecture, which 64 * arbitrates interaction between Android applications and TV interactive apps. 65 */ 66 @SystemService(Context.TV_INTERACTIVE_APP_SERVICE) 67 public final class TvInteractiveAppManager { 68 // TODO: cleanup and unhide public APIs 69 private static final String TAG = "TvInteractiveAppManager"; 70 71 /** @hide */ 72 @Retention(RetentionPolicy.SOURCE) 73 @IntDef(flag = false, prefix = "SERVICE_STATE_", value = { 74 SERVICE_STATE_UNREALIZED, 75 SERVICE_STATE_PREPARING, 76 SERVICE_STATE_READY, 77 SERVICE_STATE_ERROR}) 78 public @interface ServiceState {} 79 80 /** 81 * Unrealized state of interactive app service. 82 */ 83 public static final int SERVICE_STATE_UNREALIZED = 1; 84 /** 85 * Preparing state of interactive app service. 86 */ 87 public static final int SERVICE_STATE_PREPARING = 2; 88 /** 89 * Ready state of interactive app service. 90 * 91 * <p>In this state, the interactive app service is ready, and interactive apps can be started. 92 * 93 * @see TvInteractiveAppView#startInteractiveApp() 94 */ 95 public static final int SERVICE_STATE_READY = 3; 96 /** 97 * Error state of interactive app service. 98 */ 99 public static final int SERVICE_STATE_ERROR = 4; 100 101 102 /** @hide */ 103 @Retention(RetentionPolicy.SOURCE) 104 @IntDef(flag = false, prefix = "INTERACTIVE_APP_STATE_", value = { 105 INTERACTIVE_APP_STATE_STOPPED, 106 INTERACTIVE_APP_STATE_RUNNING, 107 INTERACTIVE_APP_STATE_ERROR}) 108 public @interface InteractiveAppState {} 109 110 /** 111 * Stopped (or not started) state of interactive application. 112 */ 113 public static final int INTERACTIVE_APP_STATE_STOPPED = 1; 114 /** 115 * Running state of interactive application. 116 */ 117 public static final int INTERACTIVE_APP_STATE_RUNNING = 2; 118 /** 119 * Error state of interactive application. 120 */ 121 public static final int INTERACTIVE_APP_STATE_ERROR = 3; 122 123 124 /** @hide */ 125 @Retention(RetentionPolicy.SOURCE) 126 @IntDef(flag = false, prefix = "ERROR_", value = { 127 ERROR_NONE, 128 ERROR_UNKNOWN, 129 ERROR_NOT_SUPPORTED, 130 ERROR_WEAK_SIGNAL, 131 ERROR_RESOURCE_UNAVAILABLE, 132 ERROR_BLOCKED, 133 ERROR_ENCRYPTED, 134 ERROR_UNKNOWN_CHANNEL, 135 }) 136 public @interface ErrorCode {} 137 138 /** 139 * No error. 140 */ 141 public static final int ERROR_NONE = 0; 142 /** 143 * Unknown error code. 144 */ 145 public static final int ERROR_UNKNOWN = 1; 146 /** 147 * Error code for an unsupported channel. 148 */ 149 public static final int ERROR_NOT_SUPPORTED = 2; 150 /** 151 * Error code for weak signal. 152 */ 153 public static final int ERROR_WEAK_SIGNAL = 3; 154 /** 155 * Error code when resource (e.g. tuner) is unavailable. 156 */ 157 public static final int ERROR_RESOURCE_UNAVAILABLE = 4; 158 /** 159 * Error code for blocked contents. 160 */ 161 public static final int ERROR_BLOCKED = 5; 162 /** 163 * Error code when the key or module is missing for the encrypted channel. 164 */ 165 public static final int ERROR_ENCRYPTED = 6; 166 /** 167 * Error code when the current channel is an unknown channel. 168 */ 169 public static final int ERROR_UNKNOWN_CHANNEL = 7; 170 171 /** @hide */ 172 @Retention(RetentionPolicy.SOURCE) 173 @IntDef(flag = false, prefix = "TELETEXT_APP_STATE_", value = { 174 TELETEXT_APP_STATE_SHOW, 175 TELETEXT_APP_STATE_HIDE, 176 TELETEXT_APP_STATE_ERROR}) 177 public @interface TeletextAppState {} 178 179 /** 180 * State of Teletext app: show 181 */ 182 public static final int TELETEXT_APP_STATE_SHOW = 1; 183 /** 184 * State of Teletext app: hide 185 */ 186 public static final int TELETEXT_APP_STATE_HIDE = 2; 187 /** 188 * State of Teletext app: error 189 */ 190 public static final int TELETEXT_APP_STATE_ERROR = 3; 191 192 /** 193 * Key for package name in app link. 194 * <p>Type: String 195 * 196 * @see #sendAppLinkCommand(String, Bundle) 197 */ 198 public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name"; 199 200 /** 201 * Key for class name in app link. 202 * <p>Type: String 203 * 204 * @see #sendAppLinkCommand(String, Bundle) 205 */ 206 public static final String APP_LINK_KEY_CLASS_NAME = "class_name"; 207 208 /** 209 * Key for command type in app link command. 210 * <p>Type: String 211 * 212 * @see #sendAppLinkCommand(String, Bundle) 213 */ 214 public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type"; 215 216 /** 217 * Key for service ID in app link command. 218 * <p>Type: String 219 * 220 * @see #sendAppLinkCommand(String, Bundle) 221 */ 222 public static final String APP_LINK_KEY_SERVICE_ID = "service_id"; 223 224 /** 225 * Key for back URI in app link command. 226 * <p>Type: String 227 * 228 * @see #sendAppLinkCommand(String, Bundle) 229 */ 230 public static final String APP_LINK_KEY_BACK_URI = "back_uri"; 231 232 /** 233 * Broadcast intent action to send app command to TV app. 234 * 235 * @see #sendAppLinkCommand(String, Bundle) 236 */ 237 public static final String ACTION_APP_LINK_COMMAND = 238 "android.media.tv.interactive.action.APP_LINK_COMMAND"; 239 240 /** 241 * Intent key for TV input ID. It's used to send app command to TV app. 242 * <p>Type: String 243 * 244 * @see #sendAppLinkCommand(String, Bundle) 245 * @see #ACTION_APP_LINK_COMMAND 246 */ 247 public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id"; 248 249 /** 250 * Intent key for TV interactive app ID. It's used to send app command to TV app. 251 * <p>Type: String 252 * 253 * @see #sendAppLinkCommand(String, Bundle) 254 * @see #ACTION_APP_LINK_COMMAND 255 * @see TvInteractiveAppServiceInfo#getId() 256 */ 257 public static final String INTENT_KEY_INTERACTIVE_APP_SERVICE_ID = "interactive_app_id"; 258 259 /** 260 * Intent key for TV channel URI. It's used to send app command to TV app. 261 * <p>Type: android.net.Uri 262 * 263 * @see #sendAppLinkCommand(String, Bundle) 264 * @see #ACTION_APP_LINK_COMMAND 265 */ 266 public static final String INTENT_KEY_CHANNEL_URI = "channel_uri"; 267 268 /** 269 * Intent key for broadcast-independent(BI) interactive app type. It's used to send app command 270 * to TV app. 271 * <p>Type: int 272 * 273 * @see #sendAppLinkCommand(String, Bundle) 274 * @see #ACTION_APP_LINK_COMMAND 275 * @see android.media.tv.interactive.TvInteractiveAppServiceInfo#getSupportedTypes() 276 * @see android.media.tv.interactive.TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle) 277 */ 278 public static final String INTENT_KEY_BI_INTERACTIVE_APP_TYPE = "bi_interactive_app_type"; 279 280 /** 281 * Intent key for broadcast-independent(BI) interactive app URI. It's used to send app command 282 * to TV app. 283 * <p>Type: android.net.Uri 284 * 285 * @see #sendAppLinkCommand(String, Bundle) 286 * @see #ACTION_APP_LINK_COMMAND 287 * @see android.media.tv.interactive.TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle) 288 */ 289 public static final String INTENT_KEY_BI_INTERACTIVE_APP_URI = "bi_interactive_app_uri"; 290 291 /** 292 * Intent key for command type. It's used to send app command to TV app. The value of this key 293 * could vary according to TV apps. 294 * <p>Type: String 295 * 296 * @see #sendAppLinkCommand(String, Bundle) 297 * @see #ACTION_APP_LINK_COMMAND 298 */ 299 public static final String INTENT_KEY_COMMAND_TYPE = "command_type"; 300 301 private final ITvInteractiveAppManager mService; 302 private final int mUserId; 303 304 // A mapping from the sequence number of a session to its SessionCallbackRecord. 305 private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap = 306 new SparseArray<>(); 307 308 // @GuardedBy("mLock") 309 private final List<TvInteractiveAppCallbackRecord> mCallbackRecords = new ArrayList<>(); 310 311 // A sequence number for the next session to be created. Should be protected by a lock 312 // {@code mSessionCallbackRecordMap}. 313 private int mNextSeq; 314 315 private final Object mLock = new Object(); 316 317 private final ITvInteractiveAppClient mClient; 318 319 /** @hide */ TvInteractiveAppManager(ITvInteractiveAppManager service, int userId)320 public TvInteractiveAppManager(ITvInteractiveAppManager service, int userId) { 321 mService = service; 322 mUserId = userId; 323 mClient = new ITvInteractiveAppClient.Stub() { 324 @Override 325 public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel, 326 int seq) { 327 synchronized (mSessionCallbackRecordMap) { 328 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 329 if (record == null) { 330 Log.e(TAG, "Callback not found for " + token); 331 return; 332 } 333 Session session = null; 334 if (token != null) { 335 session = new Session(token, channel, mService, mUserId, seq, 336 mSessionCallbackRecordMap); 337 } else { 338 mSessionCallbackRecordMap.delete(seq); 339 } 340 record.postSessionCreated(session); 341 } 342 } 343 344 @Override 345 public void onSessionReleased(int seq) { 346 synchronized (mSessionCallbackRecordMap) { 347 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 348 mSessionCallbackRecordMap.delete(seq); 349 if (record == null) { 350 Log.e(TAG, "Callback not found for seq:" + seq); 351 return; 352 } 353 record.mSession.releaseInternal(); 354 record.postSessionReleased(); 355 } 356 } 357 358 @Override 359 public void onLayoutSurface(int left, int top, int right, int bottom, int seq) { 360 synchronized (mSessionCallbackRecordMap) { 361 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 362 if (record == null) { 363 Log.e(TAG, "Callback not found for seq " + seq); 364 return; 365 } 366 record.postLayoutSurface(left, top, right, bottom); 367 } 368 } 369 370 @Override 371 public void onBroadcastInfoRequest(BroadcastInfoRequest request, int seq) { 372 synchronized (mSessionCallbackRecordMap) { 373 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 374 if (record == null) { 375 Log.e(TAG, "Callback not found for seq " + seq); 376 return; 377 } 378 record.postBroadcastInfoRequest(request); 379 } 380 } 381 382 @Override 383 public void onRemoveBroadcastInfo(int requestId, int seq) { 384 synchronized (mSessionCallbackRecordMap) { 385 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 386 if (record == null) { 387 Log.e(TAG, "Callback not found for seq " + seq); 388 return; 389 } 390 record.postRemoveBroadcastInfo(requestId); 391 } 392 } 393 394 @Override 395 public void onCommandRequest( 396 @TvInteractiveAppService.PlaybackCommandType String cmdType, 397 Bundle parameters, 398 int seq) { 399 synchronized (mSessionCallbackRecordMap) { 400 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 401 if (record == null) { 402 Log.e(TAG, "Callback not found for seq " + seq); 403 return; 404 } 405 record.postCommandRequest(cmdType, parameters); 406 } 407 } 408 409 @Override 410 public void onTimeShiftCommandRequest( 411 @TvInteractiveAppService.TimeShiftCommandType String cmdType, 412 Bundle parameters, 413 int seq) { 414 synchronized (mSessionCallbackRecordMap) { 415 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 416 if (record == null) { 417 Log.e(TAG, "Callback not found for seq " + seq); 418 return; 419 } 420 record.postTimeShiftCommandRequest(cmdType, parameters); 421 } 422 } 423 424 @Override 425 public void onSetVideoBounds(Rect rect, int seq) { 426 synchronized (mSessionCallbackRecordMap) { 427 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 428 if (record == null) { 429 Log.e(TAG, "Callback not found for seq " + seq); 430 return; 431 } 432 record.postSetVideoBounds(rect); 433 } 434 } 435 436 @Override 437 public void onAdRequest(AdRequest request, int seq) { 438 synchronized (mSessionCallbackRecordMap) { 439 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 440 if (record == null) { 441 Log.e(TAG, "Callback not found for seq " + seq); 442 return; 443 } 444 record.postAdRequest(request); 445 } 446 } 447 448 @Override 449 public void onRequestCurrentVideoBounds(int seq) { 450 synchronized (mSessionCallbackRecordMap) { 451 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 452 if (record == null) { 453 Log.e(TAG, "Callback not found for seq " + seq); 454 return; 455 } 456 record.postRequestCurrentVideoBounds(); 457 } 458 } 459 460 @Override 461 public void onRequestCurrentChannelUri(int seq) { 462 synchronized (mSessionCallbackRecordMap) { 463 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 464 if (record == null) { 465 Log.e(TAG, "Callback not found for seq " + seq); 466 return; 467 } 468 record.postRequestCurrentChannelUri(); 469 } 470 } 471 472 @Override 473 public void onRequestCurrentChannelLcn(int seq) { 474 synchronized (mSessionCallbackRecordMap) { 475 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 476 if (record == null) { 477 Log.e(TAG, "Callback not found for seq " + seq); 478 return; 479 } 480 record.postRequestCurrentChannelLcn(); 481 } 482 } 483 484 @Override 485 public void onRequestStreamVolume(int seq) { 486 synchronized (mSessionCallbackRecordMap) { 487 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 488 if (record == null) { 489 Log.e(TAG, "Callback not found for seq " + seq); 490 return; 491 } 492 record.postRequestStreamVolume(); 493 } 494 } 495 496 @Override 497 public void onRequestTrackInfoList(int seq) { 498 synchronized (mSessionCallbackRecordMap) { 499 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 500 if (record == null) { 501 Log.e(TAG, "Callback not found for seq " + seq); 502 return; 503 } 504 record.postRequestTrackInfoList(); 505 } 506 } 507 508 @Override 509 public void onRequestCurrentTvInputId(int seq) { 510 synchronized (mSessionCallbackRecordMap) { 511 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 512 if (record == null) { 513 Log.e(TAG, "Callback not found for seq " + seq); 514 return; 515 } 516 record.postRequestCurrentTvInputId(); 517 } 518 } 519 520 @Override 521 public void onRequestTimeShiftMode(int seq) { 522 synchronized (mSessionCallbackRecordMap) { 523 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 524 if (record == null) { 525 Log.e(TAG, "Callback not found for seq " + seq); 526 return; 527 } 528 record.postRequestTimeShiftMode(); 529 } 530 } 531 532 @Override 533 public void onRequestAvailableSpeeds(int seq) { 534 synchronized (mSessionCallbackRecordMap) { 535 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 536 if (record == null) { 537 Log.e(TAG, "Callback not found for seq " + seq); 538 return; 539 } 540 record.postRequestAvailableSpeeds(); 541 } 542 } 543 544 @Override 545 public void onRequestStartRecording(String requestId, Uri programUri, int seq) { 546 synchronized (mSessionCallbackRecordMap) { 547 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 548 if (record == null) { 549 Log.e(TAG, "Callback not found for seq " + seq); 550 return; 551 } 552 record.postRequestStartRecording(requestId, programUri); 553 } 554 } 555 556 @Override 557 public void onRequestStopRecording(String recordingId, int seq) { 558 synchronized (mSessionCallbackRecordMap) { 559 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 560 if (record == null) { 561 Log.e(TAG, "Callback not found for seq " + seq); 562 return; 563 } 564 record.postRequestStopRecording(recordingId); 565 } 566 } 567 568 @Override 569 public void onRequestScheduleRecording(String requestId, String inputId, Uri channelUri, 570 Uri programUri, Bundle params, int seq) { 571 synchronized (mSessionCallbackRecordMap) { 572 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 573 if (record == null) { 574 Log.e(TAG, "Callback not found for seq " + seq); 575 return; 576 } 577 record.postRequestScheduleRecording( 578 requestId, inputId, channelUri, programUri, params); 579 } 580 } 581 582 @Override 583 public void onRequestScheduleRecording2(String requestId, String inputId, 584 Uri channelUri, long startTime, long duration, int repeatDays, Bundle params, 585 int seq) { 586 synchronized (mSessionCallbackRecordMap) { 587 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 588 if (record == null) { 589 Log.e(TAG, "Callback not found for seq " + seq); 590 return; 591 } 592 record.postRequestScheduleRecording(requestId, inputId, channelUri, startTime, 593 duration, repeatDays, params); 594 } 595 } 596 597 @Override 598 public void onSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo, 599 int seq) { 600 synchronized (mSessionCallbackRecordMap) { 601 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 602 if (record == null) { 603 Log.e(TAG, "Callback not found for seq " + seq); 604 return; 605 } 606 record.postSetTvRecordingInfo(recordingId, recordingInfo); 607 } 608 } 609 610 @Override 611 public void onRequestTvRecordingInfo(String recordingId, int seq) { 612 synchronized (mSessionCallbackRecordMap) { 613 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 614 if (record == null) { 615 Log.e(TAG, "Callback not found for seq " + seq); 616 return; 617 } 618 record.postRequestTvRecordingInfo(recordingId); 619 } 620 } 621 622 @Override 623 public void onRequestTvRecordingInfoList(int type, int seq) { 624 synchronized (mSessionCallbackRecordMap) { 625 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 626 if (record == null) { 627 Log.e(TAG, "Callback not found for seq " + seq); 628 return; 629 } 630 record.postRequestTvRecordingInfoList(type); 631 } 632 } 633 634 @Override 635 public void onRequestSigning( 636 String id, String algorithm, String alias, byte[] data, int seq) { 637 synchronized (mSessionCallbackRecordMap) { 638 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 639 if (record == null) { 640 Log.e(TAG, "Callback not found for seq " + seq); 641 return; 642 } 643 record.postRequestSigning(id, algorithm, alias, data); 644 } 645 } 646 647 @Override 648 public void onSessionStateChanged(int state, int err, int seq) { 649 synchronized (mSessionCallbackRecordMap) { 650 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 651 if (record == null) { 652 Log.e(TAG, "Callback not found for seq " + seq); 653 return; 654 } 655 record.postSessionStateChanged(state, err); 656 } 657 } 658 659 @Override 660 public void onBiInteractiveAppCreated(Uri biIAppUri, String biIAppId, int seq) { 661 synchronized (mSessionCallbackRecordMap) { 662 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 663 if (record == null) { 664 Log.e(TAG, "Callback not found for seq " + seq); 665 return; 666 } 667 record.postBiInteractiveAppCreated(biIAppUri, biIAppId); 668 } 669 } 670 671 @Override 672 public void onTeletextAppStateChanged(int state, int seq) { 673 synchronized (mSessionCallbackRecordMap) { 674 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 675 if (record == null) { 676 Log.e(TAG, "Callback not found for seq " + seq); 677 return; 678 } 679 record.postTeletextAppStateChanged(state); 680 } 681 } 682 683 @Override 684 public void onAdBufferReady(AdBuffer buffer, int seq) { 685 synchronized (mSessionCallbackRecordMap) { 686 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 687 if (record == null) { 688 Log.e(TAG, "Callback not found for seq " + seq); 689 return; 690 } 691 record.postAdBufferReady(buffer); 692 } 693 } 694 }; 695 ITvInteractiveAppManagerCallback managerCallback = 696 new ITvInteractiveAppManagerCallback.Stub() { 697 @Override 698 public void onInteractiveAppServiceAdded(String iAppServiceId) { 699 synchronized (mLock) { 700 for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { 701 record.postInteractiveAppServiceAdded(iAppServiceId); 702 } 703 } 704 } 705 706 @Override 707 public void onInteractiveAppServiceRemoved(String iAppServiceId) { 708 synchronized (mLock) { 709 for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { 710 record.postInteractiveAppServiceRemoved(iAppServiceId); 711 } 712 } 713 } 714 715 @Override 716 public void onInteractiveAppServiceUpdated(String iAppServiceId) { 717 synchronized (mLock) { 718 for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { 719 record.postInteractiveAppServiceUpdated(iAppServiceId); 720 } 721 } 722 } 723 724 @Override 725 public void onTvInteractiveAppServiceInfoUpdated(TvInteractiveAppServiceInfo iAppInfo) { 726 // TODO: add public API updateInteractiveAppInfo() 727 synchronized (mLock) { 728 for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { 729 record.postTvInteractiveAppServiceInfoUpdated(iAppInfo); 730 } 731 } 732 } 733 734 @Override 735 public void onStateChanged(String iAppServiceId, int type, int state, int err) { 736 synchronized (mLock) { 737 for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { 738 record.postStateChanged(iAppServiceId, type, state, err); 739 } 740 } 741 } 742 }; 743 try { 744 if (mService != null) { 745 mService.registerCallback(managerCallback, mUserId); 746 } 747 } catch (RemoteException e) { 748 throw e.rethrowFromSystemServer(); 749 } 750 } 751 752 /** 753 * Callback used to monitor status of the TV Interactive App. 754 */ 755 public abstract static class TvInteractiveAppCallback { 756 /** 757 * This is called when a TV Interactive App service is added to the system. 758 * 759 * <p>Normally it happens when the user installs a new TV Interactive App service package 760 * that implements {@link TvInteractiveAppService} interface. 761 * 762 * @param iAppServiceId The ID of the TV Interactive App service. 763 */ onInteractiveAppServiceAdded(@onNull String iAppServiceId)764 public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) { 765 } 766 767 /** 768 * This is called when a TV Interactive App service is removed from the system. 769 * 770 * <p>Normally it happens when the user uninstalls the previously installed TV Interactive 771 * App service package. 772 * 773 * @param iAppServiceId The ID of the TV Interactive App service. 774 */ onInteractiveAppServiceRemoved(@onNull String iAppServiceId)775 public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) { 776 } 777 778 /** 779 * This is called when a TV Interactive App service is updated on the system. 780 * 781 * <p>Normally it happens when a previously installed TV Interactive App service package is 782 * re-installed or a newer version of the package exists becomes available/unavailable. 783 * 784 * @param iAppServiceId The ID of the TV Interactive App service. 785 */ onInteractiveAppServiceUpdated(@onNull String iAppServiceId)786 public void onInteractiveAppServiceUpdated(@NonNull String iAppServiceId) { 787 } 788 789 /** 790 * This is called when the information about an existing TV Interactive App service has been 791 * updated. 792 * 793 * <p>Because the system automatically creates a <code>TvInteractiveAppServiceInfo</code> 794 * object for each TV Interactive App service based on the information collected from the 795 * <code>AndroidManifest.xml</code>, this method is only called back when such information 796 * has changed dynamically. 797 * 798 * @param iAppInfo The <code>TvInteractiveAppServiceInfo</code> object that contains new 799 * information. 800 * @hide 801 */ onTvInteractiveAppServiceInfoUpdated( @onNull TvInteractiveAppServiceInfo iAppInfo)802 public void onTvInteractiveAppServiceInfoUpdated( 803 @NonNull TvInteractiveAppServiceInfo iAppInfo) { 804 } 805 806 /** 807 * This is called when the state of the interactive app service is changed. 808 * 809 * @param iAppServiceId The ID of the TV Interactive App service. 810 * @param type the interactive app type 811 * @param state the current state of the service of the given type 812 * @param err the error code for error state. {@link #ERROR_NONE} is used when the state is 813 * not {@link #SERVICE_STATE_ERROR}. 814 */ onTvInteractiveAppServiceStateChanged( @onNull String iAppServiceId, @TvInteractiveAppServiceInfo.InteractiveAppType int type, @ServiceState int state, @ErrorCode int err)815 public void onTvInteractiveAppServiceStateChanged( 816 @NonNull String iAppServiceId, 817 @TvInteractiveAppServiceInfo.InteractiveAppType int type, 818 @ServiceState int state, 819 @ErrorCode int err) { 820 } 821 } 822 823 private static final class TvInteractiveAppCallbackRecord { 824 private final TvInteractiveAppCallback mCallback; 825 private final Executor mExecutor; 826 TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor)827 TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor) { 828 mCallback = callback; 829 mExecutor = executor; 830 } 831 getCallback()832 public TvInteractiveAppCallback getCallback() { 833 return mCallback; 834 } 835 postInteractiveAppServiceAdded(final String iAppServiceId)836 public void postInteractiveAppServiceAdded(final String iAppServiceId) { 837 mExecutor.execute(new Runnable() { 838 @Override 839 public void run() { 840 mCallback.onInteractiveAppServiceAdded(iAppServiceId); 841 } 842 }); 843 } 844 postInteractiveAppServiceRemoved(final String iAppServiceId)845 public void postInteractiveAppServiceRemoved(final String iAppServiceId) { 846 mExecutor.execute(new Runnable() { 847 @Override 848 public void run() { 849 mCallback.onInteractiveAppServiceRemoved(iAppServiceId); 850 } 851 }); 852 } 853 postInteractiveAppServiceUpdated(final String iAppServiceId)854 public void postInteractiveAppServiceUpdated(final String iAppServiceId) { 855 mExecutor.execute(new Runnable() { 856 @Override 857 public void run() { 858 mCallback.onInteractiveAppServiceUpdated(iAppServiceId); 859 } 860 }); 861 } 862 postTvInteractiveAppServiceInfoUpdated( final TvInteractiveAppServiceInfo iAppInfo)863 public void postTvInteractiveAppServiceInfoUpdated( 864 final TvInteractiveAppServiceInfo iAppInfo) { 865 mExecutor.execute(new Runnable() { 866 @Override 867 public void run() { 868 mCallback.onTvInteractiveAppServiceInfoUpdated(iAppInfo); 869 } 870 }); 871 } 872 postStateChanged(String iAppServiceId, int type, int state, int err)873 public void postStateChanged(String iAppServiceId, int type, int state, int err) { 874 mExecutor.execute(new Runnable() { 875 @Override 876 public void run() { 877 mCallback.onTvInteractiveAppServiceStateChanged( 878 iAppServiceId, type, state, err); 879 } 880 }); 881 } 882 } 883 884 /** 885 * Creates a {@link Session} for a given TV interactive application. 886 * 887 * <p>The number of sessions that can be created at the same time is limited by the capability 888 * of the given interactive application. 889 * 890 * @param iAppServiceId The ID of the interactive application. 891 * @param type the type of the interactive application. 892 * @param callback A callback used to receive the created session. 893 * @param handler A {@link Handler} that the session creation will be delivered to. 894 * @hide 895 */ createSession(@onNull String iAppServiceId, int type, @NonNull final SessionCallback callback, @NonNull Handler handler)896 public void createSession(@NonNull String iAppServiceId, int type, 897 @NonNull final SessionCallback callback, @NonNull Handler handler) { 898 createSessionInternal(iAppServiceId, type, callback, handler); 899 } 900 createSessionInternal(String iAppServiceId, int type, SessionCallback callback, Handler handler)901 private void createSessionInternal(String iAppServiceId, int type, SessionCallback callback, 902 Handler handler) { 903 Preconditions.checkNotNull(iAppServiceId); 904 Preconditions.checkNotNull(callback); 905 Preconditions.checkNotNull(handler); 906 SessionCallbackRecord record = new SessionCallbackRecord(callback, handler); 907 synchronized (mSessionCallbackRecordMap) { 908 int seq = mNextSeq++; 909 mSessionCallbackRecordMap.put(seq, record); 910 try { 911 mService.createSession(mClient, iAppServiceId, type, seq, mUserId); 912 } catch (RemoteException e) { 913 throw e.rethrowFromSystemServer(); 914 } 915 } 916 } 917 918 /** 919 * Returns the complete list of TV Interactive App service on the system. 920 * 921 * @return List of {@link TvInteractiveAppServiceInfo} for each TV Interactive App service that 922 * describes its meta information. 923 */ 924 @NonNull getTvInteractiveAppServiceList()925 public List<TvInteractiveAppServiceInfo> getTvInteractiveAppServiceList() { 926 try { 927 return mService.getTvInteractiveAppServiceList(mUserId); 928 } catch (RemoteException e) { 929 throw e.rethrowFromSystemServer(); 930 } 931 } 932 933 /** 934 * Returns a list of available app link information. 935 * 936 * <P>A package must declare its app link info in its manifest using meta-data tag, so the info 937 * can be detected by the system. 938 * 939 * @return List of {@link AppLinkInfo} for each package that deslares its app link information. 940 */ 941 @NonNull getAppLinkInfoList()942 public List<AppLinkInfo> getAppLinkInfoList() { 943 try { 944 return mService.getAppLinkInfoList(mUserId); 945 } catch (RemoteException e) { 946 throw e.rethrowFromSystemServer(); 947 } 948 } 949 950 /** 951 * Registers an Android application link info record which can be used to launch the specific 952 * Android application by TV interactive App RTE. 953 * 954 * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The 955 * ID can be found in {@link TvInteractiveAppServiceInfo#getId()}. 956 * @param appLinkInfo The Android application link info record to be registered. 957 */ registerAppLinkInfo( @onNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo)958 public void registerAppLinkInfo( 959 @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) { 960 try { 961 mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId); 962 } catch (RemoteException e) { 963 throw e.rethrowFromSystemServer(); 964 } 965 } 966 967 /** 968 * Unregisters an Android application link info record which can be used to launch the specific 969 * Android application by TV interactive App RTE. 970 * 971 * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The 972 * ID can be found in {@link TvInteractiveAppServiceInfo#getId()}. 973 * @param appLinkInfo The Android application link info record to be unregistered. 974 */ unregisterAppLinkInfo( @onNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo)975 public void unregisterAppLinkInfo( 976 @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) { 977 try { 978 mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId); 979 } catch (RemoteException e) { 980 throw e.rethrowFromSystemServer(); 981 } 982 } 983 984 /** 985 * Sends app link command. 986 * 987 * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The 988 * ID can be found in {@link TvInteractiveAppServiceInfo#getId()}. 989 * @param command The command to be sent. 990 */ sendAppLinkCommand(@onNull String tvIAppServiceId, @NonNull Bundle command)991 public void sendAppLinkCommand(@NonNull String tvIAppServiceId, @NonNull Bundle command) { 992 try { 993 mService.sendAppLinkCommand(tvIAppServiceId, command, mUserId); 994 } catch (RemoteException e) { 995 throw e.rethrowFromSystemServer(); 996 } 997 } 998 999 /** 1000 * Registers a {@link TvInteractiveAppCallback}. 1001 * 1002 * @param callback A callback used to monitor status of the TV Interactive App services. 1003 * @param executor A {@link Executor} that the status change will be delivered to. 1004 */ registerCallback( @allbackExecutor @onNull Executor executor, @NonNull TvInteractiveAppCallback callback)1005 public void registerCallback( 1006 @CallbackExecutor @NonNull Executor executor, 1007 @NonNull TvInteractiveAppCallback callback) { 1008 Preconditions.checkNotNull(callback); 1009 Preconditions.checkNotNull(executor); 1010 synchronized (mLock) { 1011 mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, executor)); 1012 } 1013 } 1014 1015 /** 1016 * Unregisters the existing {@link TvInteractiveAppCallback}. 1017 * 1018 * @param callback The existing callback to remove. 1019 */ unregisterCallback(@onNull final TvInteractiveAppCallback callback)1020 public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) { 1021 Preconditions.checkNotNull(callback); 1022 synchronized (mLock) { 1023 for (Iterator<TvInteractiveAppCallbackRecord> it = mCallbackRecords.iterator(); 1024 it.hasNext(); ) { 1025 TvInteractiveAppCallbackRecord record = it.next(); 1026 if (record.getCallback() == callback) { 1027 it.remove(); 1028 break; 1029 } 1030 } 1031 } 1032 } 1033 1034 /** 1035 * The Session provides the per-session functionality of interactive app. 1036 * @hide 1037 */ 1038 public static final class Session { 1039 static final int DISPATCH_IN_PROGRESS = -1; 1040 static final int DISPATCH_NOT_HANDLED = 0; 1041 static final int DISPATCH_HANDLED = 1; 1042 1043 private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500; 1044 1045 private final ITvInteractiveAppManager mService; 1046 private final int mUserId; 1047 private final int mSeq; 1048 private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; 1049 1050 // For scheduling input event handling on the main thread. This also serves as a lock to 1051 // protect pending input events and the input channel. 1052 private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper()); 1053 1054 private TvInputManager.Session mInputSession; 1055 private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20); 1056 private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20); 1057 1058 private IBinder mToken; 1059 private TvInputEventSender mSender; 1060 private InputChannel mInputChannel; 1061 Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service, int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap)1062 private Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service, 1063 int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { 1064 mToken = token; 1065 mInputChannel = channel; 1066 mService = service; 1067 mUserId = userId; 1068 mSeq = seq; 1069 mSessionCallbackRecordMap = sessionCallbackRecordMap; 1070 } 1071 getInputSession()1072 public TvInputManager.Session getInputSession() { 1073 return mInputSession; 1074 } 1075 setInputSession(TvInputManager.Session inputSession)1076 public void setInputSession(TvInputManager.Session inputSession) { 1077 mInputSession = inputSession; 1078 } 1079 startInteractiveApp()1080 void startInteractiveApp() { 1081 if (mToken == null) { 1082 Log.w(TAG, "The session has been already released"); 1083 return; 1084 } 1085 try { 1086 mService.startInteractiveApp(mToken, mUserId); 1087 } catch (RemoteException e) { 1088 throw e.rethrowFromSystemServer(); 1089 } 1090 } 1091 stopInteractiveApp()1092 void stopInteractiveApp() { 1093 if (mToken == null) { 1094 Log.w(TAG, "The session has been already released"); 1095 return; 1096 } 1097 try { 1098 mService.stopInteractiveApp(mToken, mUserId); 1099 } catch (RemoteException e) { 1100 throw e.rethrowFromSystemServer(); 1101 } 1102 } 1103 resetInteractiveApp()1104 void resetInteractiveApp() { 1105 if (mToken == null) { 1106 Log.w(TAG, "The session has been already released"); 1107 return; 1108 } 1109 try { 1110 mService.resetInteractiveApp(mToken, mUserId); 1111 } catch (RemoteException e) { 1112 throw e.rethrowFromSystemServer(); 1113 } 1114 } 1115 createBiInteractiveApp(Uri biIAppUri, Bundle params)1116 void createBiInteractiveApp(Uri biIAppUri, Bundle params) { 1117 if (mToken == null) { 1118 Log.w(TAG, "The session has been already released"); 1119 return; 1120 } 1121 try { 1122 mService.createBiInteractiveApp(mToken, biIAppUri, params, mUserId); 1123 } catch (RemoteException e) { 1124 throw e.rethrowFromSystemServer(); 1125 } 1126 } 1127 destroyBiInteractiveApp(String biIAppId)1128 void destroyBiInteractiveApp(String biIAppId) { 1129 if (mToken == null) { 1130 Log.w(TAG, "The session has been already released"); 1131 return; 1132 } 1133 try { 1134 mService.destroyBiInteractiveApp(mToken, biIAppId, mUserId); 1135 } catch (RemoteException e) { 1136 throw e.rethrowFromSystemServer(); 1137 } 1138 } 1139 setTeletextAppEnabled(boolean enable)1140 void setTeletextAppEnabled(boolean enable) { 1141 if (mToken == null) { 1142 Log.w(TAG, "The session has been already released"); 1143 return; 1144 } 1145 try { 1146 mService.setTeletextAppEnabled(mToken, enable, mUserId); 1147 } catch (RemoteException e) { 1148 throw e.rethrowFromSystemServer(); 1149 } 1150 } 1151 sendCurrentVideoBounds(@onNull Rect bounds)1152 void sendCurrentVideoBounds(@NonNull Rect bounds) { 1153 if (mToken == null) { 1154 Log.w(TAG, "The session has been already released"); 1155 return; 1156 } 1157 try { 1158 mService.sendCurrentVideoBounds(mToken, bounds, mUserId); 1159 } catch (RemoteException e) { 1160 throw e.rethrowFromSystemServer(); 1161 } 1162 } 1163 sendCurrentChannelUri(@ullable Uri channelUri)1164 void sendCurrentChannelUri(@Nullable Uri channelUri) { 1165 if (mToken == null) { 1166 Log.w(TAG, "The session has been already released"); 1167 return; 1168 } 1169 try { 1170 mService.sendCurrentChannelUri(mToken, channelUri, mUserId); 1171 } catch (RemoteException e) { 1172 throw e.rethrowFromSystemServer(); 1173 } 1174 } 1175 sendCurrentChannelLcn(int lcn)1176 void sendCurrentChannelLcn(int lcn) { 1177 if (mToken == null) { 1178 Log.w(TAG, "The session has been already released"); 1179 return; 1180 } 1181 try { 1182 mService.sendCurrentChannelLcn(mToken, lcn, mUserId); 1183 } catch (RemoteException e) { 1184 throw e.rethrowFromSystemServer(); 1185 } 1186 } 1187 sendStreamVolume(float volume)1188 void sendStreamVolume(float volume) { 1189 if (mToken == null) { 1190 Log.w(TAG, "The session has been already released"); 1191 return; 1192 } 1193 try { 1194 mService.sendStreamVolume(mToken, volume, mUserId); 1195 } catch (RemoteException e) { 1196 throw e.rethrowFromSystemServer(); 1197 } 1198 } 1199 sendTrackInfoList(@onNull List<TvTrackInfo> tracks)1200 void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) { 1201 if (mToken == null) { 1202 Log.w(TAG, "The session has been already released"); 1203 return; 1204 } 1205 try { 1206 mService.sendTrackInfoList(mToken, tracks, mUserId); 1207 } catch (RemoteException e) { 1208 throw e.rethrowFromSystemServer(); 1209 } 1210 } 1211 sendCurrentTvInputId(@ullable String inputId)1212 void sendCurrentTvInputId(@Nullable String inputId) { 1213 if (mToken == null) { 1214 Log.w(TAG, "The session has been already released"); 1215 return; 1216 } 1217 try { 1218 mService.sendCurrentTvInputId(mToken, inputId, mUserId); 1219 } catch (RemoteException e) { 1220 throw e.rethrowFromSystemServer(); 1221 } 1222 } 1223 sendTimeShiftMode(int mode)1224 void sendTimeShiftMode(int mode) { 1225 if (mToken == null) { 1226 Log.w(TAG, "The session has been already released"); 1227 return; 1228 } 1229 try { 1230 mService.sendTimeShiftMode(mToken, mode, mUserId); 1231 } catch (RemoteException e) { 1232 throw e.rethrowFromSystemServer(); 1233 } 1234 } 1235 sendAvailableSpeeds(float[] speeds)1236 void sendAvailableSpeeds(float[] speeds) { 1237 if (mToken == null) { 1238 Log.w(TAG, "The session has been already released"); 1239 return; 1240 } 1241 try { 1242 mService.sendAvailableSpeeds(mToken, speeds, mUserId); 1243 } catch (RemoteException e) { 1244 throw e.rethrowFromSystemServer(); 1245 } 1246 } 1247 sendTvRecordingInfo(@ullable TvRecordingInfo recordingInfo)1248 void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) { 1249 if (mToken == null) { 1250 Log.w(TAG, "The session has been already released"); 1251 return; 1252 } 1253 try { 1254 mService.sendTvRecordingInfo(mToken, recordingInfo, mUserId); 1255 } catch (RemoteException e) { 1256 throw e.rethrowFromSystemServer(); 1257 } 1258 } 1259 sendTvRecordingInfoList(@ullable List<TvRecordingInfo> recordingInfoList)1260 void sendTvRecordingInfoList(@Nullable List<TvRecordingInfo> recordingInfoList) { 1261 if (mToken == null) { 1262 Log.w(TAG, "The session has been already released"); 1263 return; 1264 } 1265 try { 1266 mService.sendTvRecordingInfoList(mToken, recordingInfoList, mUserId); 1267 } catch (RemoteException e) { 1268 throw e.rethrowFromSystemServer(); 1269 } 1270 } 1271 notifyRecordingStarted(String recordingId, String requestId)1272 void notifyRecordingStarted(String recordingId, String requestId) { 1273 if (mToken == null) { 1274 Log.w(TAG, "The session has been already released"); 1275 return; 1276 } 1277 try { 1278 mService.notifyRecordingStarted(mToken, recordingId, requestId, mUserId); 1279 } catch (RemoteException e) { 1280 throw e.rethrowFromSystemServer(); 1281 } 1282 } 1283 notifyRecordingStopped(String recordingId)1284 void notifyRecordingStopped(String recordingId) { 1285 if (mToken == null) { 1286 Log.w(TAG, "The session has been already released"); 1287 return; 1288 } 1289 try { 1290 mService.notifyRecordingStopped(mToken, recordingId, mUserId); 1291 } catch (RemoteException e) { 1292 throw e.rethrowFromSystemServer(); 1293 } 1294 } 1295 sendSigningResult(@onNull String signingId, @NonNull byte[] result)1296 void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) { 1297 if (mToken == null) { 1298 Log.w(TAG, "The session has been already released"); 1299 return; 1300 } 1301 try { 1302 mService.sendSigningResult(mToken, signingId, result, mUserId); 1303 } catch (RemoteException e) { 1304 throw e.rethrowFromSystemServer(); 1305 } 1306 } 1307 notifyError(@onNull String errMsg, @NonNull Bundle params)1308 void notifyError(@NonNull String errMsg, @NonNull Bundle params) { 1309 if (mToken == null) { 1310 Log.w(TAG, "The session has been already released"); 1311 return; 1312 } 1313 try { 1314 mService.notifyError(mToken, errMsg, params, mUserId); 1315 } catch (RemoteException e) { 1316 throw e.rethrowFromSystemServer(); 1317 } 1318 } 1319 notifyTimeShiftPlaybackParams(@onNull PlaybackParams params)1320 void notifyTimeShiftPlaybackParams(@NonNull PlaybackParams params) { 1321 if (mToken == null) { 1322 Log.w(TAG, "The session has been already released"); 1323 return; 1324 } 1325 try { 1326 mService.notifyTimeShiftPlaybackParams(mToken, params, mUserId); 1327 } catch (RemoteException e) { 1328 throw e.rethrowFromSystemServer(); 1329 } 1330 } 1331 notifyTimeShiftStatusChanged( @onNull String inputId, @TvInputManager.TimeShiftStatus int status)1332 void notifyTimeShiftStatusChanged( 1333 @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) { 1334 if (mToken == null) { 1335 Log.w(TAG, "The session has been already released"); 1336 return; 1337 } 1338 try { 1339 mService.notifyTimeShiftStatusChanged(mToken, inputId, status, mUserId); 1340 } catch (RemoteException e) { 1341 throw e.rethrowFromSystemServer(); 1342 } 1343 } 1344 notifyTimeShiftStartPositionChanged(@onNull String inputId, long timeMs)1345 void notifyTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) { 1346 if (mToken == null) { 1347 Log.w(TAG, "The session has been already released"); 1348 return; 1349 } 1350 try { 1351 mService.notifyTimeShiftStartPositionChanged(mToken, inputId, timeMs, mUserId); 1352 } catch (RemoteException e) { 1353 throw e.rethrowFromSystemServer(); 1354 } 1355 } 1356 notifyTimeShiftCurrentPositionChanged(@onNull String inputId, long timeMs)1357 void notifyTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) { 1358 if (mToken == null) { 1359 Log.w(TAG, "The session has been already released"); 1360 return; 1361 } 1362 try { 1363 mService.notifyTimeShiftCurrentPositionChanged(mToken, inputId, timeMs, mUserId); 1364 } catch (RemoteException e) { 1365 throw e.rethrowFromSystemServer(); 1366 } 1367 } 1368 notifyRecordingConnectionFailed(@onNull String recordingId, @NonNull String inputId)1369 void notifyRecordingConnectionFailed(@NonNull String recordingId, @NonNull String inputId) { 1370 if (mToken == null) { 1371 Log.w(TAG, "The session has been already released"); 1372 return; 1373 } 1374 try { 1375 mService.notifyRecordingConnectionFailed(mToken, recordingId, inputId, mUserId); 1376 } catch (RemoteException e) { 1377 throw e.rethrowFromSystemServer(); 1378 } 1379 } 1380 notifyRecordingDisconnected(@onNull String recordingId, @NonNull String inputId)1381 void notifyRecordingDisconnected(@NonNull String recordingId, @NonNull String inputId) { 1382 if (mToken == null) { 1383 Log.w(TAG, "The session has been already released"); 1384 return; 1385 } 1386 try { 1387 mService.notifyRecordingDisconnected(mToken, recordingId, inputId, mUserId); 1388 } catch (RemoteException e) { 1389 throw e.rethrowFromSystemServer(); 1390 } 1391 } 1392 notifyRecordingTuned(@onNull String recordingId, @NonNull Uri channelUri)1393 void notifyRecordingTuned(@NonNull String recordingId, @NonNull Uri channelUri) { 1394 if (mToken == null) { 1395 Log.w(TAG, "The session has been already released"); 1396 return; 1397 } 1398 try { 1399 mService.notifyRecordingTuned(mToken, recordingId, channelUri, mUserId); 1400 } catch (RemoteException e) { 1401 throw e.rethrowFromSystemServer(); 1402 } 1403 } 1404 notifyRecordingError(@onNull String recordingId, int err)1405 void notifyRecordingError(@NonNull String recordingId, int err) { 1406 if (mToken == null) { 1407 Log.w(TAG, "The session has been already released"); 1408 return; 1409 } 1410 try { 1411 mService.notifyRecordingError(mToken, recordingId, err, mUserId); 1412 } catch (RemoteException e) { 1413 throw e.rethrowFromSystemServer(); 1414 } 1415 } 1416 notifyRecordingScheduled(@onNull String recordingId, @Nullable String requestId)1417 void notifyRecordingScheduled(@NonNull String recordingId, @Nullable String requestId) { 1418 if (mToken == null) { 1419 Log.w(TAG, "The session has been already released"); 1420 return; 1421 } 1422 try { 1423 mService.notifyRecordingScheduled(mToken, recordingId, requestId, mUserId); 1424 } catch (RemoteException e) { 1425 throw e.rethrowFromSystemServer(); 1426 } 1427 } 1428 1429 /** 1430 * Sets the {@link android.view.Surface} for this session. 1431 * 1432 * @param surface A {@link android.view.Surface} used to render video. 1433 */ setSurface(Surface surface)1434 public void setSurface(Surface surface) { 1435 if (mToken == null) { 1436 Log.w(TAG, "The session has been already released"); 1437 return; 1438 } 1439 // surface can be null. 1440 try { 1441 mService.setSurface(mToken, surface, mUserId); 1442 } catch (RemoteException e) { 1443 throw e.rethrowFromSystemServer(); 1444 } 1445 } 1446 1447 /** 1448 * Creates a media view. Once the media view is created, {@link #relayoutMediaView} 1449 * should be called whenever the layout of its containing view is changed. 1450 * {@link #removeMediaView()} should be called to remove the media view. 1451 * Since a session can have only one media view, this method should be called only once 1452 * or it can be called again after calling {@link #removeMediaView()}. 1453 * 1454 * @param view A view for interactive app. 1455 * @param frame A position of the media view. 1456 * @throws IllegalStateException if {@code view} is not attached to a window. 1457 */ createMediaView(@onNull View view, @NonNull Rect frame)1458 void createMediaView(@NonNull View view, @NonNull Rect frame) { 1459 Preconditions.checkNotNull(view); 1460 Preconditions.checkNotNull(frame); 1461 if (view.getWindowToken() == null) { 1462 throw new IllegalStateException("view must be attached to a window"); 1463 } 1464 if (mToken == null) { 1465 Log.w(TAG, "The session has been already released"); 1466 return; 1467 } 1468 try { 1469 mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId); 1470 } catch (RemoteException e) { 1471 throw e.rethrowFromSystemServer(); 1472 } 1473 } 1474 1475 /** 1476 * Relayouts the current media view. 1477 * 1478 * @param frame A new position of the media view. 1479 */ relayoutMediaView(@onNull Rect frame)1480 void relayoutMediaView(@NonNull Rect frame) { 1481 Preconditions.checkNotNull(frame); 1482 if (mToken == null) { 1483 Log.w(TAG, "The session has been already released"); 1484 return; 1485 } 1486 try { 1487 mService.relayoutMediaView(mToken, frame, mUserId); 1488 } catch (RemoteException e) { 1489 throw e.rethrowFromSystemServer(); 1490 } 1491 } 1492 1493 /** 1494 * Removes the current media view. 1495 */ removeMediaView()1496 void removeMediaView() { 1497 if (mToken == null) { 1498 Log.w(TAG, "The session has been already released"); 1499 return; 1500 } 1501 try { 1502 mService.removeMediaView(mToken, mUserId); 1503 } catch (RemoteException e) { 1504 throw e.rethrowFromSystemServer(); 1505 } 1506 } 1507 1508 /** 1509 * Notifies of any structural changes (format or size) of the surface passed in 1510 * {@link #setSurface}. 1511 * 1512 * @param format The new PixelFormat of the surface. 1513 * @param width The new width of the surface. 1514 * @param height The new height of the surface. 1515 */ dispatchSurfaceChanged(int format, int width, int height)1516 public void dispatchSurfaceChanged(int format, int width, int height) { 1517 if (mToken == null) { 1518 Log.w(TAG, "The session has been already released"); 1519 return; 1520 } 1521 try { 1522 mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId); 1523 } catch (RemoteException e) { 1524 throw e.rethrowFromSystemServer(); 1525 } 1526 } 1527 1528 /** 1529 * Dispatches an input event to this session. 1530 * 1531 * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}. 1532 * @param token A token used to identify the input event later in the callback. 1533 * @param callback A callback used to receive the dispatch result. Cannot be {@code null}. 1534 * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be 1535 * {@code null}. 1536 * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns 1537 * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns 1538 * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will 1539 * be invoked later. 1540 * @hide 1541 */ dispatchInputEvent(@onNull InputEvent event, Object token, @NonNull FinishedInputEventCallback callback, @NonNull Handler handler)1542 public int dispatchInputEvent(@NonNull InputEvent event, Object token, 1543 @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) { 1544 Preconditions.checkNotNull(event); 1545 Preconditions.checkNotNull(callback); 1546 Preconditions.checkNotNull(handler); 1547 synchronized (mHandler) { 1548 if (mInputChannel == null) { 1549 return DISPATCH_NOT_HANDLED; 1550 } 1551 PendingEvent p = obtainPendingEventLocked(event, token, callback, handler); 1552 if (Looper.myLooper() == Looper.getMainLooper()) { 1553 // Already running on the main thread so we can send the event immediately. 1554 return sendInputEventOnMainLooperLocked(p); 1555 } 1556 1557 // Post the event to the main thread. 1558 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p); 1559 msg.setAsynchronous(true); 1560 mHandler.sendMessage(msg); 1561 return DISPATCH_IN_PROGRESS; 1562 } 1563 } 1564 1565 /** 1566 * Notifies of any broadcast info response passed in from TIS. 1567 * 1568 * @param response response passed in from TIS. 1569 */ notifyBroadcastInfoResponse(BroadcastInfoResponse response)1570 public void notifyBroadcastInfoResponse(BroadcastInfoResponse response) { 1571 if (mToken == null) { 1572 Log.w(TAG, "The session has been already released"); 1573 return; 1574 } 1575 try { 1576 mService.notifyBroadcastInfoResponse(mToken, response, mUserId); 1577 } catch (RemoteException e) { 1578 throw e.rethrowFromSystemServer(); 1579 } 1580 } 1581 1582 /** 1583 * Notifies of any advertisement response passed in from TIS. 1584 * 1585 * @param response response passed in from TIS. 1586 */ notifyAdResponse(AdResponse response)1587 public void notifyAdResponse(AdResponse response) { 1588 if (mToken == null) { 1589 Log.w(TAG, "The session has been already released"); 1590 return; 1591 } 1592 try { 1593 mService.notifyAdResponse(mToken, response, mUserId); 1594 } catch (RemoteException e) { 1595 throw e.rethrowFromSystemServer(); 1596 } 1597 } 1598 1599 /** 1600 * Notifies the advertisement buffer is consumed. 1601 */ notifyAdBufferConsumed(AdBuffer buffer)1602 public void notifyAdBufferConsumed(AdBuffer buffer) { 1603 if (mToken == null) { 1604 Log.w(TAG, "The session has been already released"); 1605 return; 1606 } 1607 try { 1608 mService.notifyAdBufferConsumed(mToken, buffer, mUserId); 1609 } catch (RemoteException e) { 1610 throw e.rethrowFromSystemServer(); 1611 } finally { 1612 if (buffer != null) { 1613 buffer.getSharedMemory().close(); 1614 } 1615 } 1616 } 1617 1618 /** 1619 * Releases this session. 1620 */ release()1621 public void release() { 1622 if (mToken == null) { 1623 Log.w(TAG, "The session has been already released"); 1624 return; 1625 } 1626 try { 1627 mService.releaseSession(mToken, mUserId); 1628 } catch (RemoteException e) { 1629 throw e.rethrowFromSystemServer(); 1630 } 1631 1632 releaseInternal(); 1633 } 1634 1635 /** 1636 * Notifies Interactive APP session when a channel is tuned. 1637 */ notifyTuned(Uri channelUri)1638 public void notifyTuned(Uri channelUri) { 1639 if (mToken == null) { 1640 Log.w(TAG, "The session has been already released"); 1641 return; 1642 } 1643 try { 1644 mService.notifyTuned(mToken, channelUri, mUserId); 1645 } catch (RemoteException e) { 1646 throw e.rethrowFromSystemServer(); 1647 } 1648 } 1649 1650 /** 1651 * Notifies Interactive APP session when a track is selected. 1652 */ notifyTrackSelected(int type, String trackId)1653 public void notifyTrackSelected(int type, String trackId) { 1654 if (mToken == null) { 1655 Log.w(TAG, "The session has been already released"); 1656 return; 1657 } 1658 try { 1659 mService.notifyTrackSelected(mToken, type, trackId, mUserId); 1660 } catch (RemoteException e) { 1661 throw e.rethrowFromSystemServer(); 1662 } 1663 } 1664 1665 /** 1666 * Notifies Interactive APP session when tracks are changed. 1667 */ notifyTracksChanged(List<TvTrackInfo> tracks)1668 public void notifyTracksChanged(List<TvTrackInfo> tracks) { 1669 if (mToken == null) { 1670 Log.w(TAG, "The session has been already released"); 1671 return; 1672 } 1673 try { 1674 mService.notifyTracksChanged(mToken, tracks, mUserId); 1675 } catch (RemoteException e) { 1676 throw e.rethrowFromSystemServer(); 1677 } 1678 } 1679 1680 /** 1681 * Notifies Interactive APP session when video is available. 1682 */ notifyVideoAvailable()1683 public void notifyVideoAvailable() { 1684 if (mToken == null) { 1685 Log.w(TAG, "The session has been already released"); 1686 return; 1687 } 1688 try { 1689 mService.notifyVideoAvailable(mToken, mUserId); 1690 } catch (RemoteException e) { 1691 throw e.rethrowFromSystemServer(); 1692 } 1693 } 1694 1695 /** 1696 * Notifies Interactive APP session when video is unavailable. 1697 */ notifyVideoUnavailable(int reason)1698 public void notifyVideoUnavailable(int reason) { 1699 if (mToken == null) { 1700 Log.w(TAG, "The session has been already released"); 1701 return; 1702 } 1703 try { 1704 mService.notifyVideoUnavailable(mToken, reason, mUserId); 1705 } catch (RemoteException e) { 1706 throw e.rethrowFromSystemServer(); 1707 } 1708 } 1709 1710 /** 1711 * Notifies Interactive APP session when content is allowed. 1712 */ notifyContentAllowed()1713 public void notifyContentAllowed() { 1714 if (mToken == null) { 1715 Log.w(TAG, "The session has been already released"); 1716 return; 1717 } 1718 try { 1719 mService.notifyContentAllowed(mToken, mUserId); 1720 } catch (RemoteException e) { 1721 throw e.rethrowFromSystemServer(); 1722 } 1723 } 1724 1725 /** 1726 * Notifies Interactive APP session when content is blocked. 1727 */ notifyContentBlocked(TvContentRating rating)1728 public void notifyContentBlocked(TvContentRating rating) { 1729 if (mToken == null) { 1730 Log.w(TAG, "The session has been already released"); 1731 return; 1732 } 1733 try { 1734 mService.notifyContentBlocked(mToken, rating.flattenToString(), mUserId); 1735 } catch (RemoteException e) { 1736 throw e.rethrowFromSystemServer(); 1737 } 1738 } 1739 1740 /** 1741 * Notifies Interactive APP session when signal strength is changed. 1742 */ notifySignalStrength(int strength)1743 public void notifySignalStrength(int strength) { 1744 if (mToken == null) { 1745 Log.w(TAG, "The session has been already released"); 1746 return; 1747 } 1748 try { 1749 mService.notifySignalStrength(mToken, strength, mUserId); 1750 } catch (RemoteException e) { 1751 throw e.rethrowFromSystemServer(); 1752 } 1753 } 1754 1755 /** 1756 * Notifies Interactive APP session when a new TV message is received. 1757 */ notifyTvMessage(int type, Bundle data)1758 public void notifyTvMessage(int type, Bundle data) { 1759 if (mToken == null) { 1760 Log.w(TAG, "The session has been already released"); 1761 return; 1762 } 1763 try { 1764 mService.notifyTvMessage(mToken, type, data, mUserId); 1765 } catch (RemoteException e) { 1766 throw e.rethrowFromSystemServer(); 1767 } 1768 } 1769 flushPendingEventsLocked()1770 private void flushPendingEventsLocked() { 1771 mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT); 1772 1773 final int count = mPendingEvents.size(); 1774 for (int i = 0; i < count; i++) { 1775 int seq = mPendingEvents.keyAt(i); 1776 Message msg = mHandler.obtainMessage( 1777 InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0); 1778 msg.setAsynchronous(true); 1779 msg.sendToTarget(); 1780 } 1781 } 1782 releaseInternal()1783 private void releaseInternal() { 1784 mToken = null; 1785 synchronized (mHandler) { 1786 if (mInputChannel != null) { 1787 if (mSender != null) { 1788 flushPendingEventsLocked(); 1789 mSender.dispose(); 1790 mSender = null; 1791 } 1792 mInputChannel.dispose(); 1793 mInputChannel = null; 1794 } 1795 } 1796 synchronized (mSessionCallbackRecordMap) { 1797 mSessionCallbackRecordMap.delete(mSeq); 1798 } 1799 } 1800 obtainPendingEventLocked(InputEvent event, Object token, FinishedInputEventCallback callback, Handler handler)1801 private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, 1802 FinishedInputEventCallback callback, Handler handler) { 1803 PendingEvent p = mPendingEventPool.acquire(); 1804 if (p == null) { 1805 p = new PendingEvent(); 1806 } 1807 p.mEvent = event; 1808 p.mEventToken = token; 1809 p.mCallback = callback; 1810 p.mEventHandler = handler; 1811 return p; 1812 } 1813 1814 // Assumes the event has already been removed from the queue. invokeFinishedInputEventCallback(PendingEvent p, boolean handled)1815 void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { 1816 p.mHandled = handled; 1817 if (p.mEventHandler.getLooper().isCurrentThread()) { 1818 // Already running on the callback handler thread so we can send the callback 1819 // immediately. 1820 p.run(); 1821 } else { 1822 // Post the event to the callback handler thread. 1823 // In this case, the callback will be responsible for recycling the event. 1824 Message msg = Message.obtain(p.mEventHandler, p); 1825 msg.setAsynchronous(true); 1826 msg.sendToTarget(); 1827 } 1828 } 1829 1830 // Must be called on the main looper sendInputEventAndReportResultOnMainLooper(PendingEvent p)1831 private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) { 1832 synchronized (mHandler) { 1833 int result = sendInputEventOnMainLooperLocked(p); 1834 if (result == DISPATCH_IN_PROGRESS) { 1835 return; 1836 } 1837 } 1838 1839 invokeFinishedInputEventCallback(p, false); 1840 } 1841 sendInputEventOnMainLooperLocked(PendingEvent p)1842 private int sendInputEventOnMainLooperLocked(PendingEvent p) { 1843 if (mInputChannel != null) { 1844 if (mSender == null) { 1845 mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper()); 1846 } 1847 1848 final InputEvent event = p.mEvent; 1849 final int seq = event.getSequenceNumber(); 1850 if (mSender.sendInputEvent(seq, event)) { 1851 mPendingEvents.put(seq, p); 1852 Message msg = mHandler.obtainMessage( 1853 InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); 1854 msg.setAsynchronous(true); 1855 mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT); 1856 return DISPATCH_IN_PROGRESS; 1857 } 1858 1859 Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:" 1860 + event); 1861 } 1862 return DISPATCH_NOT_HANDLED; 1863 } 1864 finishedInputEvent(int seq, boolean handled, boolean timeout)1865 void finishedInputEvent(int seq, boolean handled, boolean timeout) { 1866 final PendingEvent p; 1867 synchronized (mHandler) { 1868 int index = mPendingEvents.indexOfKey(seq); 1869 if (index < 0) { 1870 return; // spurious, event already finished or timed out 1871 } 1872 1873 p = mPendingEvents.valueAt(index); 1874 mPendingEvents.removeAt(index); 1875 1876 if (timeout) { 1877 Log.w(TAG, "Timeout waiting for session to handle input event after " 1878 + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken); 1879 } else { 1880 mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); 1881 } 1882 } 1883 1884 invokeFinishedInputEventCallback(p, handled); 1885 } 1886 recyclePendingEventLocked(PendingEvent p)1887 private void recyclePendingEventLocked(PendingEvent p) { 1888 p.recycle(); 1889 mPendingEventPool.release(p); 1890 } 1891 1892 /** 1893 * Callback that is invoked when an input event that was dispatched to this session has been 1894 * finished. 1895 * 1896 * @hide 1897 */ 1898 public interface FinishedInputEventCallback { 1899 /** 1900 * Called when the dispatched input event is finished. 1901 * 1902 * @param token A token passed to {@link #dispatchInputEvent}. 1903 * @param handled {@code true} if the dispatched input event was handled properly. 1904 * {@code false} otherwise. 1905 */ onFinishedInputEvent(Object token, boolean handled)1906 void onFinishedInputEvent(Object token, boolean handled); 1907 } 1908 1909 private final class InputEventHandler extends Handler { 1910 public static final int MSG_SEND_INPUT_EVENT = 1; 1911 public static final int MSG_TIMEOUT_INPUT_EVENT = 2; 1912 public static final int MSG_FLUSH_INPUT_EVENT = 3; 1913 InputEventHandler(Looper looper)1914 InputEventHandler(Looper looper) { 1915 super(looper, null, true); 1916 } 1917 1918 @Override handleMessage(Message msg)1919 public void handleMessage(Message msg) { 1920 switch (msg.what) { 1921 case MSG_SEND_INPUT_EVENT: { 1922 sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj); 1923 return; 1924 } 1925 case MSG_TIMEOUT_INPUT_EVENT: { 1926 finishedInputEvent(msg.arg1, false, true); 1927 return; 1928 } 1929 case MSG_FLUSH_INPUT_EVENT: { 1930 finishedInputEvent(msg.arg1, false, false); 1931 return; 1932 } 1933 } 1934 } 1935 } 1936 1937 private final class TvInputEventSender extends InputEventSender { TvInputEventSender(InputChannel inputChannel, Looper looper)1938 TvInputEventSender(InputChannel inputChannel, Looper looper) { 1939 super(inputChannel, looper); 1940 } 1941 1942 @Override onInputEventFinished(int seq, boolean handled)1943 public void onInputEventFinished(int seq, boolean handled) { 1944 finishedInputEvent(seq, handled, false); 1945 } 1946 } 1947 1948 private final class PendingEvent implements Runnable { 1949 public InputEvent mEvent; 1950 public Object mEventToken; 1951 public FinishedInputEventCallback mCallback; 1952 public Handler mEventHandler; 1953 public boolean mHandled; 1954 recycle()1955 public void recycle() { 1956 mEvent = null; 1957 mEventToken = null; 1958 mCallback = null; 1959 mEventHandler = null; 1960 mHandled = false; 1961 } 1962 1963 @Override run()1964 public void run() { 1965 mCallback.onFinishedInputEvent(mEventToken, mHandled); 1966 1967 synchronized (mEventHandler) { 1968 recyclePendingEventLocked(this); 1969 } 1970 } 1971 } 1972 } 1973 1974 private static final class SessionCallbackRecord { 1975 private final SessionCallback mSessionCallback; 1976 private final Handler mHandler; 1977 private Session mSession; 1978 SessionCallbackRecord(SessionCallback sessionCallback, Handler handler)1979 SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) { 1980 mSessionCallback = sessionCallback; 1981 mHandler = handler; 1982 } 1983 postSessionCreated(final Session session)1984 void postSessionCreated(final Session session) { 1985 mSession = session; 1986 mHandler.post(new Runnable() { 1987 @Override 1988 public void run() { 1989 mSessionCallback.onSessionCreated(session); 1990 } 1991 }); 1992 } 1993 postSessionReleased()1994 void postSessionReleased() { 1995 mHandler.post(new Runnable() { 1996 @Override 1997 public void run() { 1998 mSessionCallback.onSessionReleased(mSession); 1999 } 2000 }); 2001 } 2002 postLayoutSurface(final int left, final int top, final int right, final int bottom)2003 void postLayoutSurface(final int left, final int top, final int right, 2004 final int bottom) { 2005 mHandler.post(new Runnable() { 2006 @Override 2007 public void run() { 2008 mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom); 2009 } 2010 }); 2011 } 2012 postBroadcastInfoRequest(final BroadcastInfoRequest request)2013 void postBroadcastInfoRequest(final BroadcastInfoRequest request) { 2014 mHandler.post(new Runnable() { 2015 @Override 2016 public void run() { 2017 if (mSession.getInputSession() != null) { 2018 mSession.getInputSession().requestBroadcastInfo(request); 2019 } 2020 } 2021 }); 2022 } 2023 postRemoveBroadcastInfo(final int requestId)2024 void postRemoveBroadcastInfo(final int requestId) { 2025 mHandler.post(new Runnable() { 2026 @Override 2027 public void run() { 2028 if (mSession.getInputSession() != null) { 2029 mSession.getInputSession().removeBroadcastInfo(requestId); 2030 } 2031 } 2032 }); 2033 } 2034 postCommandRequest( final @TvInteractiveAppService.PlaybackCommandType String cmdType, final Bundle parameters)2035 void postCommandRequest( 2036 final @TvInteractiveAppService.PlaybackCommandType String cmdType, 2037 final Bundle parameters) { 2038 mHandler.post(new Runnable() { 2039 @Override 2040 public void run() { 2041 mSessionCallback.onCommandRequest(mSession, cmdType, parameters); 2042 } 2043 }); 2044 } 2045 postTimeShiftCommandRequest( final @TvInteractiveAppService.TimeShiftCommandType String cmdType, final Bundle parameters)2046 void postTimeShiftCommandRequest( 2047 final @TvInteractiveAppService.TimeShiftCommandType String cmdType, 2048 final Bundle parameters) { 2049 mHandler.post(new Runnable() { 2050 @Override 2051 public void run() { 2052 mSessionCallback.onTimeShiftCommandRequest(mSession, cmdType, parameters); 2053 } 2054 }); 2055 } 2056 postSetVideoBounds(Rect rect)2057 void postSetVideoBounds(Rect rect) { 2058 mHandler.post(new Runnable() { 2059 @Override 2060 public void run() { 2061 mSessionCallback.onSetVideoBounds(mSession, rect); 2062 } 2063 }); 2064 } 2065 postRequestCurrentVideoBounds()2066 void postRequestCurrentVideoBounds() { 2067 mHandler.post(new Runnable() { 2068 @Override 2069 public void run() { 2070 mSessionCallback.onRequestCurrentVideoBounds(mSession); 2071 } 2072 }); 2073 } 2074 postRequestCurrentChannelUri()2075 void postRequestCurrentChannelUri() { 2076 mHandler.post(new Runnable() { 2077 @Override 2078 public void run() { 2079 mSessionCallback.onRequestCurrentChannelUri(mSession); 2080 } 2081 }); 2082 } 2083 postRequestCurrentChannelLcn()2084 void postRequestCurrentChannelLcn() { 2085 mHandler.post(new Runnable() { 2086 @Override 2087 public void run() { 2088 mSessionCallback.onRequestCurrentChannelLcn(mSession); 2089 } 2090 }); 2091 } 2092 postRequestStreamVolume()2093 void postRequestStreamVolume() { 2094 mHandler.post(new Runnable() { 2095 @Override 2096 public void run() { 2097 mSessionCallback.onRequestStreamVolume(mSession); 2098 } 2099 }); 2100 } 2101 postRequestTrackInfoList()2102 void postRequestTrackInfoList() { 2103 mHandler.post(new Runnable() { 2104 @Override 2105 public void run() { 2106 mSessionCallback.onRequestTrackInfoList(mSession); 2107 } 2108 }); 2109 } 2110 postRequestCurrentTvInputId()2111 void postRequestCurrentTvInputId() { 2112 mHandler.post(new Runnable() { 2113 @Override 2114 public void run() { 2115 mSessionCallback.onRequestCurrentTvInputId(mSession); 2116 } 2117 }); 2118 } 2119 postRequestTimeShiftMode()2120 void postRequestTimeShiftMode() { 2121 mHandler.post(new Runnable() { 2122 @Override 2123 public void run() { 2124 mSessionCallback.onRequestTimeShiftMode(mSession); 2125 } 2126 }); 2127 } 2128 postRequestAvailableSpeeds()2129 void postRequestAvailableSpeeds() { 2130 mHandler.post(new Runnable() { 2131 @Override 2132 public void run() { 2133 mSessionCallback.onRequestAvailableSpeeds(mSession); 2134 } 2135 }); 2136 } 2137 postRequestStartRecording(String requestId, Uri programUri)2138 void postRequestStartRecording(String requestId, Uri programUri) { 2139 mHandler.post(new Runnable() { 2140 @Override 2141 public void run() { 2142 mSessionCallback.onRequestStartRecording(mSession, requestId, programUri); 2143 } 2144 }); 2145 } 2146 postRequestStopRecording(String recordingId)2147 void postRequestStopRecording(String recordingId) { 2148 mHandler.post(new Runnable() { 2149 @Override 2150 public void run() { 2151 mSessionCallback.onRequestStopRecording(mSession, recordingId); 2152 } 2153 }); 2154 } 2155 postRequestScheduleRecording(String requestId, String inputId, Uri channelUri, Uri programUri, Bundle params)2156 void postRequestScheduleRecording(String requestId, String inputId, Uri channelUri, 2157 Uri programUri, Bundle params) { 2158 mHandler.post(new Runnable() { 2159 @Override 2160 public void run() { 2161 mSessionCallback.onRequestScheduleRecording( 2162 mSession, requestId, inputId, channelUri, programUri, params); 2163 } 2164 }); 2165 } 2166 postRequestScheduleRecording(String requestId, String inputId, Uri channelUri, long startTime, long duration, int repeatDays, Bundle params)2167 void postRequestScheduleRecording(String requestId, String inputId, Uri channelUri, 2168 long startTime, long duration, int repeatDays, Bundle params) { 2169 mHandler.post(new Runnable() { 2170 @Override 2171 public void run() { 2172 mSessionCallback.onRequestScheduleRecording(mSession, requestId, inputId, 2173 channelUri, startTime, duration, repeatDays, params); 2174 } 2175 }); 2176 } 2177 postRequestSigning(String id, String algorithm, String alias, byte[] data)2178 void postRequestSigning(String id, String algorithm, String alias, byte[] data) { 2179 mHandler.post(new Runnable() { 2180 @Override 2181 public void run() { 2182 mSessionCallback.onRequestSigning(mSession, id, algorithm, alias, data); 2183 } 2184 }); 2185 } 2186 postRequestTvRecordingInfo(String recordingId)2187 void postRequestTvRecordingInfo(String recordingId) { 2188 mHandler.post(new Runnable() { 2189 @Override 2190 public void run() { 2191 mSessionCallback.onRequestTvRecordingInfo(mSession, recordingId); 2192 } 2193 }); 2194 } 2195 postRequestTvRecordingInfoList(int type)2196 void postRequestTvRecordingInfoList(int type) { 2197 mHandler.post(new Runnable() { 2198 @Override 2199 public void run() { 2200 mSessionCallback.onRequestTvRecordingInfoList(mSession, type); 2201 } 2202 }); 2203 } 2204 postSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo)2205 void postSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo) { 2206 mHandler.post(new Runnable() { 2207 @Override 2208 public void run() { 2209 mSessionCallback.onSetTvRecordingInfo(mSession, recordingId, recordingInfo); 2210 } 2211 }); 2212 } 2213 postAdRequest(final AdRequest request)2214 void postAdRequest(final AdRequest request) { 2215 mHandler.post(new Runnable() { 2216 @Override 2217 public void run() { 2218 if (mSession.getInputSession() != null) { 2219 mSession.getInputSession().requestAd(request); 2220 } 2221 } 2222 }); 2223 } 2224 postSessionStateChanged(int state, int err)2225 void postSessionStateChanged(int state, int err) { 2226 mHandler.post(new Runnable() { 2227 @Override 2228 public void run() { 2229 mSessionCallback.onSessionStateChanged(mSession, state, err); 2230 } 2231 }); 2232 } 2233 postBiInteractiveAppCreated(Uri biIAppUri, String biIAppId)2234 void postBiInteractiveAppCreated(Uri biIAppUri, String biIAppId) { 2235 mHandler.post(new Runnable() { 2236 @Override 2237 public void run() { 2238 mSessionCallback.onBiInteractiveAppCreated(mSession, biIAppUri, biIAppId); 2239 } 2240 }); 2241 } 2242 postTeletextAppStateChanged(int state)2243 void postTeletextAppStateChanged(int state) { 2244 mHandler.post(new Runnable() { 2245 @Override 2246 public void run() { 2247 mSessionCallback.onTeletextAppStateChanged(mSession, state); 2248 } 2249 }); 2250 } 2251 postAdBufferReady(AdBuffer buffer)2252 void postAdBufferReady(AdBuffer buffer) { 2253 mHandler.post(new Runnable() { 2254 @Override 2255 public void run() { 2256 if (mSession.getInputSession() != null) { 2257 mSession.getInputSession().notifyAdBufferReady(buffer); 2258 } 2259 } 2260 }); 2261 } 2262 } 2263 2264 /** 2265 * Interface used to receive the created session. 2266 * @hide 2267 */ 2268 public abstract static class SessionCallback { 2269 /** 2270 * This is called after {@link TvInteractiveAppManager#createSession} has been processed. 2271 * 2272 * @param session A {@link TvInteractiveAppManager.Session} instance created. This can be 2273 * {@code null} if the creation request failed. 2274 */ onSessionCreated(@ullable Session session)2275 public void onSessionCreated(@Nullable Session session) { 2276 } 2277 2278 /** 2279 * This is called when {@link TvInteractiveAppManager.Session} is released. 2280 * This typically happens when the process hosting the session has crashed or been killed. 2281 * 2282 * @param session the {@link TvInteractiveAppManager.Session} instance released. 2283 */ onSessionReleased(@onNull Session session)2284 public void onSessionReleased(@NonNull Session session) { 2285 } 2286 2287 /** 2288 * This is called when {@link TvInteractiveAppService.Session#layoutSurface} is called to 2289 * change the layout of surface. 2290 * 2291 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2292 * @param left Left position. 2293 * @param top Top position. 2294 * @param right Right position. 2295 * @param bottom Bottom position. 2296 */ onLayoutSurface(Session session, int left, int top, int right, int bottom)2297 public void onLayoutSurface(Session session, int left, int top, int right, int bottom) { 2298 } 2299 2300 /** 2301 * This is called when {@link TvInteractiveAppService.Session#requestCommand} is called. 2302 * 2303 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2304 * @param cmdType type of the command. 2305 * @param parameters parameters of the command. 2306 */ onCommandRequest( Session session, @TvInteractiveAppService.PlaybackCommandType String cmdType, Bundle parameters)2307 public void onCommandRequest( 2308 Session session, 2309 @TvInteractiveAppService.PlaybackCommandType String cmdType, 2310 Bundle parameters) { 2311 } 2312 2313 /** 2314 * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftCommand} is 2315 * called. 2316 * 2317 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2318 * @param cmdType type of the time shift command. 2319 * @param parameters parameters of the command. 2320 */ onTimeShiftCommandRequest( Session session, @TvInteractiveAppService.TimeShiftCommandType String cmdType, Bundle parameters)2321 public void onTimeShiftCommandRequest( 2322 Session session, 2323 @TvInteractiveAppService.TimeShiftCommandType String cmdType, 2324 Bundle parameters) { 2325 } 2326 2327 /** 2328 * This is called when {@link TvInteractiveAppService.Session#setVideoBounds} is called. 2329 * 2330 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2331 */ onSetVideoBounds(Session session, Rect rect)2332 public void onSetVideoBounds(Session session, Rect rect) { 2333 } 2334 2335 /** 2336 * This is called when {@link TvInteractiveAppService.Session#requestCurrentVideoBounds} is 2337 * called. 2338 * 2339 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2340 */ onRequestCurrentVideoBounds(Session session)2341 public void onRequestCurrentVideoBounds(Session session) { 2342 } 2343 2344 /** 2345 * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelUri} is 2346 * called. 2347 * 2348 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2349 */ onRequestCurrentChannelUri(Session session)2350 public void onRequestCurrentChannelUri(Session session) { 2351 } 2352 2353 /** 2354 * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelLcn} is 2355 * called. 2356 * 2357 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2358 */ onRequestCurrentChannelLcn(Session session)2359 public void onRequestCurrentChannelLcn(Session session) { 2360 } 2361 2362 /** 2363 * This is called when {@link TvInteractiveAppService.Session#requestStreamVolume} is 2364 * called. 2365 * 2366 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2367 */ onRequestStreamVolume(Session session)2368 public void onRequestStreamVolume(Session session) { 2369 } 2370 2371 /** 2372 * This is called when {@link TvInteractiveAppService.Session#requestTrackInfoList} is 2373 * called. 2374 * 2375 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2376 */ onRequestTrackInfoList(Session session)2377 public void onRequestTrackInfoList(Session session) { 2378 } 2379 2380 /** 2381 * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId} is 2382 * called. 2383 * 2384 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2385 */ onRequestCurrentTvInputId(Session session)2386 public void onRequestCurrentTvInputId(Session session) { 2387 } 2388 2389 /** 2390 * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftMode()} is 2391 * called. 2392 * 2393 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2394 */ onRequestTimeShiftMode(Session session)2395 public void onRequestTimeShiftMode(Session session) { 2396 } 2397 2398 /** 2399 * This is called when {@link TvInteractiveAppService.Session#requestAvailableSpeeds()} is 2400 * called. 2401 * 2402 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2403 */ onRequestAvailableSpeeds(Session session)2404 public void onRequestAvailableSpeeds(Session session) { 2405 } 2406 2407 /** 2408 * This is called when {@link TvInteractiveAppService.Session#requestStartRecording} is 2409 * called. 2410 * 2411 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2412 * @param programUri The Uri of the program to be recorded. 2413 */ onRequestStartRecording(Session session, String requestId, Uri programUri)2414 public void onRequestStartRecording(Session session, String requestId, Uri programUri) { 2415 } 2416 2417 /** 2418 * This is called when {@link TvInteractiveAppService.Session#requestStopRecording(String)} 2419 * is called. 2420 * 2421 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2422 * @param recordingId The recordingId of the recording to be stopped. 2423 */ onRequestStopRecording(Session session, String recordingId)2424 public void onRequestStopRecording(Session session, String recordingId) { 2425 } 2426 2427 /** 2428 * This is called when 2429 * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, Uri, Bundle)} 2430 * is called. 2431 * 2432 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2433 * @param inputId The ID of the TV input for the given channel. 2434 * @param channelUri The URI of a channel to be recorded. 2435 * @param programUri The URI of the TV program to be recorded. 2436 * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped 2437 * name, i.e. prefixed with a package name you own, so that different developers 2438 * will not create conflicting keys. 2439 * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle) 2440 * @see android.media.tv.TvRecordingClient#startRecording(Uri) 2441 */ onRequestScheduleRecording(Session session, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri, @NonNull Bundle params)2442 public void onRequestScheduleRecording(Session session, @NonNull String requestId, 2443 @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri, 2444 @NonNull Bundle params) { 2445 } 2446 2447 /** 2448 * This is called when 2449 * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, long, long, int, Bundle)} 2450 * is called. 2451 * 2452 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2453 * @param inputId The ID of the TV input for the given channel. 2454 * @param channelUri The URI of a channel to be recorded. 2455 * @param startTime The start time of the recording in milliseconds since epoch. 2456 * @param duration The duration of the recording in milliseconds. 2457 * @param repeatDays The repeated days. 0 if not repeated. 2458 * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped 2459 * name, i.e. prefixed with a package name you own, so that different developers 2460 * will not create conflicting keys. 2461 * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle) 2462 * @see android.media.tv.TvRecordingClient#startRecording(Uri) 2463 */ onRequestScheduleRecording(Session session, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration, int repeatDays, @NonNull Bundle params)2464 public void onRequestScheduleRecording(Session session, @NonNull String requestId, 2465 @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration, 2466 int repeatDays, @NonNull Bundle params) { 2467 } 2468 2469 /** 2470 * This is called when 2471 * {@link TvInteractiveAppService.Session#setTvRecordingInfo(String, TvRecordingInfo)} is 2472 * called. 2473 * 2474 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2475 * @param recordingId The recordingId of the recording which will have the info set. 2476 * @param recordingInfo The recording info to set to the recording. 2477 */ onSetTvRecordingInfo(Session session, String recordingId, TvRecordingInfo recordingInfo)2478 public void onSetTvRecordingInfo(Session session, String recordingId, 2479 TvRecordingInfo recordingInfo) { 2480 } 2481 2482 /** 2483 * This is called when {@link TvInteractiveAppService.Session#requestTvRecordingInfo} is 2484 * called. 2485 * 2486 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2487 * @param recordingId The recordingId of the recording to be stopped. 2488 */ onRequestTvRecordingInfo(Session session, String recordingId)2489 public void onRequestTvRecordingInfo(Session session, String recordingId) { 2490 } 2491 2492 /** 2493 * This is called when {@link TvInteractiveAppService.Session#requestTvRecordingInfoList} is 2494 * called. 2495 * 2496 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2497 * @param type The type of recordings to return 2498 */ onRequestTvRecordingInfoList(Session session, @TvRecordingInfo.TvRecordingListType int type)2499 public void onRequestTvRecordingInfoList(Session session, 2500 @TvRecordingInfo.TvRecordingListType int type) { 2501 } 2502 2503 /** 2504 * This is called when 2505 * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is 2506 * called. 2507 * 2508 * @param session A {@link TvInteractiveAppService.Session} associated with this callback. 2509 * @param signingId the ID to identify the request. 2510 * @param algorithm the standard name of the signature algorithm requested, such as 2511 * MD5withRSA, SHA256withDSA, etc. 2512 * @param alias the alias of the corresponding {@link java.security.KeyStore}. 2513 * @param data the original bytes to be signed. 2514 */ onRequestSigning( Session session, String signingId, String algorithm, String alias, byte[] data)2515 public void onRequestSigning( 2516 Session session, String signingId, String algorithm, String alias, byte[] data) { 2517 } 2518 2519 /** 2520 * This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is 2521 * called. 2522 * 2523 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2524 * @param state the current state. 2525 */ onSessionStateChanged( Session session, @InteractiveAppState int state, @ErrorCode int err)2526 public void onSessionStateChanged( 2527 Session session, 2528 @InteractiveAppState int state, 2529 @ErrorCode int err) { 2530 } 2531 2532 /** 2533 * This is called when {@link TvInteractiveAppService.Session#notifyBiInteractiveAppCreated} 2534 * is called. 2535 * 2536 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2537 * @param biIAppUri URI associated this BI interactive app. This is the same URI in 2538 * {@link Session#createBiInteractiveApp(Uri, Bundle)} 2539 * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive 2540 * app. 2541 */ onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId)2542 public void onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId) { 2543 } 2544 2545 /** 2546 * This is called when {@link TvInteractiveAppService.Session#notifyTeletextAppStateChanged} 2547 * is called. 2548 * 2549 * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. 2550 * @param state the current state. 2551 */ onTeletextAppStateChanged( Session session, @TvInteractiveAppManager.TeletextAppState int state)2552 public void onTeletextAppStateChanged( 2553 Session session, @TvInteractiveAppManager.TeletextAppState int state) { 2554 } 2555 } 2556 } 2557