1 /* 2 * Copyright (C) 2014 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; 18 19 import android.annotation.SystemApi; 20 import android.graphics.Rect; 21 import android.net.Uri; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.os.IBinder; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.os.RemoteException; 28 import android.util.ArrayMap; 29 import android.util.Log; 30 import android.util.Pools.Pool; 31 import android.util.Pools.SimplePool; 32 import android.util.SparseArray; 33 import android.view.InputChannel; 34 import android.view.InputEvent; 35 import android.view.InputEventSender; 36 import android.view.KeyEvent; 37 import android.view.Surface; 38 import android.view.View; 39 40 import java.util.ArrayList; 41 import java.util.Iterator; 42 import java.util.LinkedList; 43 import java.util.List; 44 import java.util.Map; 45 46 /** 47 * Central system API to the overall TV input framework (TIF) architecture, which arbitrates 48 * interaction between applications and the selected TV inputs. 49 */ 50 public final class TvInputManager { 51 private static final String TAG = "TvInputManager"; 52 53 static final int VIDEO_UNAVAILABLE_REASON_START = 0; 54 static final int VIDEO_UNAVAILABLE_REASON_END = 3; 55 56 /** 57 * A generic reason. Video is not available due to an unspecified error. 58 */ 59 public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = VIDEO_UNAVAILABLE_REASON_START; 60 /** 61 * Video is not available because the TV input is in the middle of tuning to a new channel. 62 */ 63 public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; 64 /** 65 * Video is not available due to the weak TV signal. 66 */ 67 public static final int VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2; 68 /** 69 * Video is not available because the TV input stopped the playback temporarily to buffer more 70 * data. 71 */ 72 public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = VIDEO_UNAVAILABLE_REASON_END; 73 74 /** 75 * The TV input is in unknown state. 76 * <p> 77 * State for denoting unknown TV input state. The typical use case is when a requested TV 78 * input is removed from the device or it is not registered. Used in 79 * {@code ITvInputManager.getTvInputState()}. 80 * </p> 81 * @hide 82 */ 83 public static final int INPUT_STATE_UNKNOWN = -1; 84 85 /** 86 * The TV input is connected. 87 * <p> 88 * State for {@link #getInputState} and {@link 89 * TvInputManager.TvInputCallback#onInputStateChanged}. 90 * </p> 91 */ 92 public static final int INPUT_STATE_CONNECTED = 0; 93 /** 94 * The TV input is connected but in standby mode. It would take a while until it becomes 95 * fully ready. 96 * <p> 97 * State for {@link #getInputState} and {@link 98 * TvInputManager.TvInputCallback#onInputStateChanged}. 99 * </p> 100 */ 101 public static final int INPUT_STATE_CONNECTED_STANDBY = 1; 102 /** 103 * The TV input is disconnected. 104 * <p> 105 * State for {@link #getInputState} and {@link 106 * TvInputManager.TvInputCallback#onInputStateChanged}. 107 * </p> 108 */ 109 public static final int INPUT_STATE_DISCONNECTED = 2; 110 111 /** 112 * Broadcast intent action when the user blocked content ratings change. For use with the 113 * {@link #isRatingBlocked}. 114 */ 115 public static final String ACTION_BLOCKED_RATINGS_CHANGED = 116 "android.media.tv.action.BLOCKED_RATINGS_CHANGED"; 117 118 /** 119 * Broadcast intent action when the parental controls enabled state changes. For use with the 120 * {@link #isParentalControlsEnabled}. 121 */ 122 public static final String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = 123 "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; 124 125 /** 126 * Broadcast intent action used to query available content rating systems. 127 * <p> 128 * The TV input manager service locates available content rating systems by querying broadcast 129 * receivers that are registered for this action. An application can offer additional content 130 * rating systems to the user by declaring a suitable broadcast receiver in its manifest. 131 * </p><p> 132 * Here is an example broadcast receiver declaration that an application might include in its 133 * AndroidManifest.xml to advertise custom content rating systems. The meta-data specifies a 134 * resource that contains a description of each content rating system that is provided by the 135 * application. 136 * <p><pre class="prettyprint"> 137 * {@literal 138 * <receiver android:name=".TvInputReceiver"> 139 * <intent-filter> 140 * <action android:name= 141 * "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" /> 142 * </intent-filter> 143 * <meta-data 144 * android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS" 145 * android:resource="@xml/tv_content_rating_systems" /> 146 * </receiver>}</pre></p> 147 * In the above example, the <code>@xml/tv_content_rating_systems</code> resource refers to an 148 * XML resource whose root element is <code><rating-system-definitions></code> that 149 * contains zero or more <code><rating-system-definition></code> elements. Each <code> 150 * <rating-system-definition></code> element specifies the ratings, sub-ratings and rating 151 * orders of a particular content rating system. 152 * </p> 153 * 154 * @see TvContentRating 155 */ 156 public static final String ACTION_QUERY_CONTENT_RATING_SYSTEMS = 157 "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS"; 158 159 /** 160 * Content rating systems metadata associated with {@link #ACTION_QUERY_CONTENT_RATING_SYSTEMS}. 161 * <p> 162 * Specifies the resource ID of an XML resource that describes the content rating systems that 163 * are provided by the application. 164 * </p> 165 */ 166 public static final String META_DATA_CONTENT_RATING_SYSTEMS = 167 "android.media.tv.metadata.CONTENT_RATING_SYSTEMS"; 168 169 private final ITvInputManager mService; 170 171 private final Object mLock = new Object(); 172 173 // @GuardedBy("mLock") 174 private final List<TvInputCallbackRecord> mCallbackRecords = 175 new LinkedList<TvInputCallbackRecord>(); 176 177 // A mapping from TV input ID to the state of corresponding input. 178 // @GuardedBy("mLock") 179 private final Map<String, Integer> mStateMap = new ArrayMap<String, Integer>(); 180 181 // A mapping from the sequence number of a session to its SessionCallbackRecord. 182 private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap = 183 new SparseArray<SessionCallbackRecord>(); 184 185 // A sequence number for the next session to be created. Should be protected by a lock 186 // {@code mSessionCallbackRecordMap}. 187 private int mNextSeq; 188 189 private final ITvInputClient mClient; 190 191 private final ITvInputManagerCallback mManagerCallback; 192 193 private final int mUserId; 194 195 /** 196 * Interface used to receive the created session. 197 * @hide 198 */ 199 @SystemApi 200 public abstract static class SessionCallback { 201 /** 202 * This is called after {@link TvInputManager#createSession} has been processed. 203 * 204 * @param session A {@link TvInputManager.Session} instance created. This can be 205 * {@code null} if the creation request failed. 206 */ 207 public void onSessionCreated(Session session) { 208 } 209 210 /** 211 * This is called when {@link TvInputManager.Session} is released. 212 * This typically happens when the process hosting the session has crashed or been killed. 213 * 214 * @param session A {@link TvInputManager.Session} instance released. 215 */ 216 public void onSessionReleased(Session session) { 217 } 218 219 /** 220 * This is called when the channel of this session is changed by the underlying TV input 221 * without any {@link TvInputManager.Session#tune(Uri)} request. 222 * 223 * @param session A {@link TvInputManager.Session} associated with this callback. 224 * @param channelUri The URI of a channel. 225 */ 226 public void onChannelRetuned(Session session, Uri channelUri) { 227 } 228 229 /** 230 * This is called when the track information of the session has been changed. 231 * 232 * @param session A {@link TvInputManager.Session} associated with this callback. 233 * @param tracks A list which includes track information. 234 */ 235 public void onTracksChanged(Session session, List<TvTrackInfo> tracks) { 236 } 237 238 /** 239 * This is called when a track for a given type is selected. 240 * 241 * @param session A {@link TvInputManager.Session} associated with this callback. 242 * @param type The type of the selected track. The type can be 243 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or 244 * {@link TvTrackInfo#TYPE_SUBTITLE}. 245 * @param trackId The ID of the selected track. When {@code null} the currently selected 246 * track for a given type should be unselected. 247 */ 248 public void onTrackSelected(Session session, int type, String trackId) { 249 } 250 251 /** 252 * This is invoked when the video size has been changed. It is also called when the first 253 * time video size information becomes available after the session is tuned to a specific 254 * channel. 255 * 256 * @param session A {@link TvInputManager.Session} associated with this callback. 257 * @param width The width of the video. 258 * @param height The height of the video. 259 */ 260 public void onVideoSizeChanged(Session session, int width, int height) { 261 } 262 263 /** 264 * This is called when the video is available, so the TV input starts the playback. 265 * 266 * @param session A {@link TvInputManager.Session} associated with this callback. 267 */ 268 public void onVideoAvailable(Session session) { 269 } 270 271 /** 272 * This is called when the video is not available, so the TV input stops the playback. 273 * 274 * @param session A {@link TvInputManager.Session} associated with this callback 275 * @param reason The reason why the TV input stopped the playback: 276 * <ul> 277 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN} 278 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} 279 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL} 280 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING} 281 * </ul> 282 */ 283 public void onVideoUnavailable(Session session, int reason) { 284 } 285 286 /** 287 * This is called when the current program content turns out to be allowed to watch since 288 * its content rating is not blocked by parental controls. 289 * 290 * @param session A {@link TvInputManager.Session} associated with this callback 291 */ 292 public void onContentAllowed(Session session) { 293 } 294 295 /** 296 * This is called when the current program content turns out to be not allowed to watch 297 * since its content rating is blocked by parental controls. 298 * 299 * @param session A {@link TvInputManager.Session} associated with this callback 300 * @param rating The content ration of the blocked program. 301 */ 302 public void onContentBlocked(Session session, TvContentRating rating) { 303 } 304 305 /** 306 * This is called when {@link TvInputService.Session#layoutSurface} is called to change the 307 * layout of surface. 308 * 309 * @param session A {@link TvInputManager.Session} associated with this callback 310 * @param left Left position. 311 * @param top Top position. 312 * @param right Right position. 313 * @param bottom Bottom position. 314 * @hide 315 */ 316 @SystemApi 317 public void onLayoutSurface(Session session, int left, int top, int right, int bottom) { 318 } 319 320 /** 321 * This is called when a custom event has been sent from this session. 322 * 323 * @param session A {@link TvInputManager.Session} associated with this callback 324 * @param eventType The type of the event. 325 * @param eventArgs Optional arguments of the event. 326 * @hide 327 */ 328 @SystemApi 329 public void onSessionEvent(Session session, String eventType, Bundle eventArgs) { 330 } 331 } 332 333 private static final class SessionCallbackRecord { 334 private final SessionCallback mSessionCallback; 335 private final Handler mHandler; 336 private Session mSession; 337 338 SessionCallbackRecord(SessionCallback sessionCallback, 339 Handler handler) { 340 mSessionCallback = sessionCallback; 341 mHandler = handler; 342 } 343 344 void postSessionCreated(final Session session) { 345 mSession = session; 346 mHandler.post(new Runnable() { 347 @Override 348 public void run() { 349 mSessionCallback.onSessionCreated(session); 350 } 351 }); 352 } 353 354 void postSessionReleased() { 355 mHandler.post(new Runnable() { 356 @Override 357 public void run() { 358 mSessionCallback.onSessionReleased(mSession); 359 } 360 }); 361 } 362 363 void postChannelRetuned(final Uri channelUri) { 364 mHandler.post(new Runnable() { 365 @Override 366 public void run() { 367 mSessionCallback.onChannelRetuned(mSession, channelUri); 368 } 369 }); 370 } 371 372 void postTracksChanged(final List<TvTrackInfo> tracks) { 373 mHandler.post(new Runnable() { 374 @Override 375 public void run() { 376 mSessionCallback.onTracksChanged(mSession, tracks); 377 } 378 }); 379 } 380 381 void postTrackSelected(final int type, final String trackId) { 382 mHandler.post(new Runnable() { 383 @Override 384 public void run() { 385 mSessionCallback.onTrackSelected(mSession, type, trackId); 386 } 387 }); 388 } 389 390 void postVideoSizeChanged(final int width, final int height) { 391 mHandler.post(new Runnable() { 392 @Override 393 public void run() { 394 mSessionCallback.onVideoSizeChanged(mSession, width, height); 395 } 396 }); 397 } 398 399 void postVideoAvailable() { 400 mHandler.post(new Runnable() { 401 @Override 402 public void run() { 403 mSessionCallback.onVideoAvailable(mSession); 404 } 405 }); 406 } 407 408 void postVideoUnavailable(final int reason) { 409 mHandler.post(new Runnable() { 410 @Override 411 public void run() { 412 mSessionCallback.onVideoUnavailable(mSession, reason); 413 } 414 }); 415 } 416 417 void postContentAllowed() { 418 mHandler.post(new Runnable() { 419 @Override 420 public void run() { 421 mSessionCallback.onContentAllowed(mSession); 422 } 423 }); 424 } 425 426 void postContentBlocked(final TvContentRating rating) { 427 mHandler.post(new Runnable() { 428 @Override 429 public void run() { 430 mSessionCallback.onContentBlocked(mSession, rating); 431 } 432 }); 433 } 434 435 void postLayoutSurface(final int left, final int top, final int right, 436 final int bottom) { 437 mHandler.post(new Runnable() { 438 @Override 439 public void run() { 440 mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom); 441 } 442 }); 443 } 444 445 void postSessionEvent(final String eventType, final Bundle eventArgs) { 446 mHandler.post(new Runnable() { 447 @Override 448 public void run() { 449 mSessionCallback.onSessionEvent(mSession, eventType, eventArgs); 450 } 451 }); 452 } 453 } 454 455 /** 456 * Callback used to monitor status of the TV input. 457 */ 458 public abstract static class TvInputCallback { 459 /** 460 * This is called when the state of a given TV input is changed. 461 * 462 * @param inputId The id of the TV input. 463 * @param state State of the TV input. The value is one of the following: 464 * <ul> 465 * <li>{@link TvInputManager#INPUT_STATE_CONNECTED} 466 * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY} 467 * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED} 468 * </ul> 469 */ 470 public void onInputStateChanged(String inputId, int state) { 471 } 472 473 /** 474 * This is called when a TV input is added. 475 * 476 * @param inputId The id of the TV input. 477 */ 478 public void onInputAdded(String inputId) { 479 } 480 481 /** 482 * This is called when a TV input is removed. 483 * 484 * @param inputId The id of the TV input. 485 */ 486 public void onInputRemoved(String inputId) { 487 } 488 489 /** 490 * This is called when a TV input is updated. The update of TV input happens when it is 491 * reinstalled or the media on which the newer version of TV input exists is 492 * available/unavailable. 493 * 494 * @param inputId The id of the TV input. 495 * @hide 496 */ 497 @SystemApi 498 public void onInputUpdated(String inputId) { 499 } 500 } 501 502 private static final class TvInputCallbackRecord { 503 private final TvInputCallback mCallback; 504 private final Handler mHandler; 505 506 public TvInputCallbackRecord(TvInputCallback callback, Handler handler) { 507 mCallback = callback; 508 mHandler = handler; 509 } 510 511 public TvInputCallback getCallback() { 512 return mCallback; 513 } 514 515 public void postInputStateChanged(final String inputId, final int state) { 516 mHandler.post(new Runnable() { 517 @Override 518 public void run() { 519 mCallback.onInputStateChanged(inputId, state); 520 } 521 }); 522 } 523 524 public void postInputAdded(final String inputId) { 525 mHandler.post(new Runnable() { 526 @Override 527 public void run() { 528 mCallback.onInputAdded(inputId); 529 } 530 }); 531 } 532 533 public void postInputRemoved(final String inputId) { 534 mHandler.post(new Runnable() { 535 @Override 536 public void run() { 537 mCallback.onInputRemoved(inputId); 538 } 539 }); 540 } 541 542 public void postInputUpdated(final String inputId) { 543 mHandler.post(new Runnable() { 544 @Override 545 public void run() { 546 mCallback.onInputUpdated(inputId); 547 } 548 }); 549 } 550 } 551 552 /** 553 * Interface used to receive events from Hardware objects. 554 * @hide 555 */ 556 @SystemApi 557 public abstract static class HardwareCallback { 558 public abstract void onReleased(); 559 public abstract void onStreamConfigChanged(TvStreamConfig[] configs); 560 } 561 562 /** 563 * @hide 564 */ 565 public TvInputManager(ITvInputManager service, int userId) { 566 mService = service; 567 mUserId = userId; 568 mClient = new ITvInputClient.Stub() { 569 @Override 570 public void onSessionCreated(String inputId, IBinder token, InputChannel channel, 571 int seq) { 572 synchronized (mSessionCallbackRecordMap) { 573 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 574 if (record == null) { 575 Log.e(TAG, "Callback not found for " + token); 576 return; 577 } 578 Session session = null; 579 if (token != null) { 580 session = new Session(token, channel, mService, mUserId, seq, 581 mSessionCallbackRecordMap); 582 } 583 record.postSessionCreated(session); 584 } 585 } 586 587 @Override 588 public void onSessionReleased(int seq) { 589 synchronized (mSessionCallbackRecordMap) { 590 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 591 mSessionCallbackRecordMap.delete(seq); 592 if (record == null) { 593 Log.e(TAG, "Callback not found for seq:" + seq); 594 return; 595 } 596 record.mSession.releaseInternal(); 597 record.postSessionReleased(); 598 } 599 } 600 601 @Override 602 public void onChannelRetuned(Uri channelUri, int seq) { 603 synchronized (mSessionCallbackRecordMap) { 604 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 605 if (record == null) { 606 Log.e(TAG, "Callback not found for seq " + seq); 607 return; 608 } 609 record.postChannelRetuned(channelUri); 610 } 611 } 612 613 @Override 614 public void onTracksChanged(List<TvTrackInfo> tracks, int seq) { 615 synchronized (mSessionCallbackRecordMap) { 616 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 617 if (record == null) { 618 Log.e(TAG, "Callback not found for seq " + seq); 619 return; 620 } 621 if (record.mSession.updateTracks(tracks)) { 622 record.postTracksChanged(tracks); 623 postVideoSizeChangedIfNeededLocked(record); 624 } 625 } 626 } 627 628 @Override 629 public void onTrackSelected(int type, String trackId, int seq) { 630 synchronized (mSessionCallbackRecordMap) { 631 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 632 if (record == null) { 633 Log.e(TAG, "Callback not found for seq " + seq); 634 return; 635 } 636 if (record.mSession.updateTrackSelection(type, trackId)) { 637 record.postTrackSelected(type, trackId); 638 postVideoSizeChangedIfNeededLocked(record); 639 } 640 } 641 } 642 643 private void postVideoSizeChangedIfNeededLocked(SessionCallbackRecord record) { 644 TvTrackInfo track = record.mSession.getVideoTrackToNotify(); 645 if (track != null) { 646 record.postVideoSizeChanged(track.getVideoWidth(), track.getVideoHeight()); 647 } 648 } 649 650 @Override 651 public void onVideoAvailable(int seq) { 652 synchronized (mSessionCallbackRecordMap) { 653 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 654 if (record == null) { 655 Log.e(TAG, "Callback not found for seq " + seq); 656 return; 657 } 658 record.postVideoAvailable(); 659 } 660 } 661 662 @Override 663 public void onVideoUnavailable(int reason, int seq) { 664 synchronized (mSessionCallbackRecordMap) { 665 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 666 if (record == null) { 667 Log.e(TAG, "Callback not found for seq " + seq); 668 return; 669 } 670 record.postVideoUnavailable(reason); 671 } 672 } 673 674 @Override 675 public void onContentAllowed(int seq) { 676 synchronized (mSessionCallbackRecordMap) { 677 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 678 if (record == null) { 679 Log.e(TAG, "Callback not found for seq " + seq); 680 return; 681 } 682 record.postContentAllowed(); 683 } 684 } 685 686 @Override 687 public void onContentBlocked(String rating, int seq) { 688 synchronized (mSessionCallbackRecordMap) { 689 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 690 if (record == null) { 691 Log.e(TAG, "Callback not found for seq " + seq); 692 return; 693 } 694 record.postContentBlocked(TvContentRating.unflattenFromString(rating)); 695 } 696 } 697 698 @Override 699 public void onLayoutSurface(int left, int top, int right, int bottom, int seq) { 700 synchronized (mSessionCallbackRecordMap) { 701 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 702 if (record == null) { 703 Log.e(TAG, "Callback not found for seq " + seq); 704 return; 705 } 706 record.postLayoutSurface(left, top, right, bottom); 707 } 708 } 709 710 @Override 711 public void onSessionEvent(String eventType, Bundle eventArgs, int seq) { 712 synchronized (mSessionCallbackRecordMap) { 713 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 714 if (record == null) { 715 Log.e(TAG, "Callback not found for seq " + seq); 716 return; 717 } 718 record.postSessionEvent(eventType, eventArgs); 719 } 720 } 721 }; 722 mManagerCallback = new ITvInputManagerCallback.Stub() { 723 @Override 724 public void onInputStateChanged(String inputId, int state) { 725 synchronized (mLock) { 726 mStateMap.put(inputId, state); 727 for (TvInputCallbackRecord record : mCallbackRecords) { 728 record.postInputStateChanged(inputId, state); 729 } 730 } 731 } 732 733 @Override 734 public void onInputAdded(String inputId) { 735 synchronized (mLock) { 736 mStateMap.put(inputId, INPUT_STATE_CONNECTED); 737 for (TvInputCallbackRecord record : mCallbackRecords) { 738 record.postInputAdded(inputId); 739 } 740 } 741 } 742 743 @Override 744 public void onInputRemoved(String inputId) { 745 synchronized (mLock) { 746 mStateMap.remove(inputId); 747 for (TvInputCallbackRecord record : mCallbackRecords) { 748 record.postInputRemoved(inputId); 749 } 750 } 751 } 752 753 @Override 754 public void onInputUpdated(String inputId) { 755 synchronized (mLock) { 756 for (TvInputCallbackRecord record : mCallbackRecords) { 757 record.postInputUpdated(inputId); 758 } 759 } 760 } 761 }; 762 try { 763 if (mService != null) { 764 mService.registerCallback(mManagerCallback, mUserId); 765 List<TvInputInfo> infos = mService.getTvInputList(mUserId); 766 synchronized (mLock) { 767 for (TvInputInfo info : infos) { 768 String inputId = info.getId(); 769 int state = mService.getTvInputState(inputId, mUserId); 770 if (state != INPUT_STATE_UNKNOWN) { 771 mStateMap.put(inputId, state); 772 } 773 } 774 } 775 } 776 } catch (RemoteException e) { 777 Log.e(TAG, "TvInputManager initialization failed: " + e); 778 } 779 } 780 781 /** 782 * Returns the complete list of TV inputs on the system. 783 * 784 * @return List of {@link TvInputInfo} for each TV input that describes its meta information. 785 */ 786 public List<TvInputInfo> getTvInputList() { 787 try { 788 return mService.getTvInputList(mUserId); 789 } catch (RemoteException e) { 790 throw new RuntimeException(e); 791 } 792 } 793 794 /** 795 * Returns the {@link TvInputInfo} for a given TV input. 796 * 797 * @param inputId The ID of the TV input. 798 * @return the {@link TvInputInfo} for a given TV input. {@code null} if not found. 799 */ 800 public TvInputInfo getTvInputInfo(String inputId) { 801 if (inputId == null) { 802 throw new IllegalArgumentException("inputId cannot be null"); 803 } 804 try { 805 return mService.getTvInputInfo(inputId, mUserId); 806 } catch (RemoteException e) { 807 throw new RuntimeException(e); 808 } 809 } 810 811 /** 812 * Returns the state of a given TV input. It returns one of the following: 813 * <ul> 814 * <li>{@link #INPUT_STATE_CONNECTED} 815 * <li>{@link #INPUT_STATE_CONNECTED_STANDBY} 816 * <li>{@link #INPUT_STATE_DISCONNECTED} 817 * </ul> 818 * 819 * @param inputId The id of the TV input. 820 * @throws IllegalArgumentException if the argument is {@code null} or if there is no 821 * {@link TvInputInfo} corresponding to {@code inputId}. 822 */ 823 public int getInputState(String inputId) { 824 if (inputId == null) { 825 throw new IllegalArgumentException("inputId cannot be null"); 826 } 827 synchronized (mLock) { 828 Integer state = mStateMap.get(inputId); 829 if (state == null) { 830 throw new IllegalArgumentException("Unrecognized input ID: " + inputId); 831 } 832 return state.intValue(); 833 } 834 } 835 836 /** 837 * Registers a {@link TvInputCallback}. 838 * 839 * @param callback A callback used to monitor status of the TV inputs. 840 * @param handler A {@link Handler} that the status change will be delivered to. 841 * @throws IllegalArgumentException if any of the arguments is {@code null}. 842 */ 843 public void registerCallback(TvInputCallback callback, Handler handler) { 844 if (callback == null) { 845 throw new IllegalArgumentException("callback cannot be null"); 846 } 847 if (handler == null) { 848 throw new IllegalArgumentException("handler cannot be null"); 849 } 850 synchronized (mLock) { 851 mCallbackRecords.add(new TvInputCallbackRecord(callback, handler)); 852 } 853 } 854 855 /** 856 * Unregisters the existing {@link TvInputCallback}. 857 * 858 * @param callback The existing callback to remove. 859 * @throws IllegalArgumentException if any of the arguments is {@code null}. 860 */ 861 public void unregisterCallback(final TvInputCallback callback) { 862 if (callback == null) { 863 throw new IllegalArgumentException("callback cannot be null"); 864 } 865 synchronized (mLock) { 866 for (Iterator<TvInputCallbackRecord> it = mCallbackRecords.iterator(); 867 it.hasNext(); ) { 868 TvInputCallbackRecord record = it.next(); 869 if (record.getCallback() == callback) { 870 it.remove(); 871 break; 872 } 873 } 874 } 875 } 876 877 /** 878 * Returns the user's parental controls enabled state. 879 * 880 * @return {@code true} if the user enabled the parental controls, {@code false} otherwise. 881 */ 882 public boolean isParentalControlsEnabled() { 883 try { 884 return mService.isParentalControlsEnabled(mUserId); 885 } catch (RemoteException e) { 886 throw new RuntimeException(e); 887 } 888 } 889 890 /** 891 * Sets the user's parental controls enabled state. 892 * 893 * @param enabled The user's parental controls enabled state. {@code true} if the user enabled 894 * the parental controls, {@code false} otherwise. 895 * @see #isParentalControlsEnabled 896 * @hide 897 */ 898 @SystemApi 899 public void setParentalControlsEnabled(boolean enabled) { 900 try { 901 mService.setParentalControlsEnabled(enabled, mUserId); 902 } catch (RemoteException e) { 903 throw new RuntimeException(e); 904 } 905 } 906 907 /** 908 * Checks whether a given TV content rating is blocked by the user. 909 * 910 * @param rating The TV content rating to check. 911 * @return {@code true} if the given TV content rating is blocked, {@code false} otherwise. 912 */ 913 public boolean isRatingBlocked(TvContentRating rating) { 914 if (rating == null) { 915 throw new IllegalArgumentException("rating cannot be null"); 916 } 917 try { 918 return mService.isRatingBlocked(rating.flattenToString(), mUserId); 919 } catch (RemoteException e) { 920 throw new RuntimeException(e); 921 } 922 } 923 924 /** 925 * Returns the list of blocked content ratings. 926 * 927 * @return the list of content ratings blocked by the user. 928 * @hide 929 */ 930 @SystemApi 931 public List<TvContentRating> getBlockedRatings() { 932 try { 933 List<TvContentRating> ratings = new ArrayList<TvContentRating>(); 934 for (String rating : mService.getBlockedRatings(mUserId)) { 935 ratings.add(TvContentRating.unflattenFromString(rating)); 936 } 937 return ratings; 938 } catch (RemoteException e) { 939 throw new RuntimeException(e); 940 } 941 } 942 943 /** 944 * Adds a user blocked content rating. 945 * 946 * @param rating The content rating to block. 947 * @see #isRatingBlocked 948 * @see #removeBlockedRating 949 * @hide 950 */ 951 @SystemApi 952 public void addBlockedRating(TvContentRating rating) { 953 if (rating == null) { 954 throw new IllegalArgumentException("rating cannot be null"); 955 } 956 try { 957 mService.addBlockedRating(rating.flattenToString(), mUserId); 958 } catch (RemoteException e) { 959 throw new RuntimeException(e); 960 } 961 } 962 963 /** 964 * Removes a user blocked content rating. 965 * 966 * @param rating The content rating to unblock. 967 * @see #isRatingBlocked 968 * @see #addBlockedRating 969 * @hide 970 */ 971 @SystemApi 972 public void removeBlockedRating(TvContentRating rating) { 973 if (rating == null) { 974 throw new IllegalArgumentException("rating cannot be null"); 975 } 976 try { 977 mService.removeBlockedRating(rating.flattenToString(), mUserId); 978 } catch (RemoteException e) { 979 throw new RuntimeException(e); 980 } 981 } 982 983 /** 984 * Returns the list of all TV content rating systems defined. 985 * @hide 986 */ 987 @SystemApi 988 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList() { 989 try { 990 return mService.getTvContentRatingSystemList(mUserId); 991 } catch (RemoteException e) { 992 throw new RuntimeException(e); 993 } 994 } 995 996 /** 997 * Creates a {@link Session} for a given TV input. 998 * <p> 999 * The number of sessions that can be created at the same time is limited by the capability of 1000 * the given TV input. 1001 * </p> 1002 * 1003 * @param inputId The id of the TV input. 1004 * @param callback A callback used to receive the created session. 1005 * @param handler A {@link Handler} that the session creation will be delivered to. 1006 * @throws IllegalArgumentException if any of the arguments is {@code null}. 1007 * @hide 1008 */ 1009 @SystemApi 1010 public void createSession(String inputId, final SessionCallback callback, 1011 Handler handler) { 1012 if (inputId == null) { 1013 throw new IllegalArgumentException("id cannot be null"); 1014 } 1015 if (callback == null) { 1016 throw new IllegalArgumentException("callback cannot be null"); 1017 } 1018 if (handler == null) { 1019 throw new IllegalArgumentException("handler cannot be null"); 1020 } 1021 SessionCallbackRecord record = new SessionCallbackRecord(callback, handler); 1022 synchronized (mSessionCallbackRecordMap) { 1023 int seq = mNextSeq++; 1024 mSessionCallbackRecordMap.put(seq, record); 1025 try { 1026 mService.createSession(mClient, inputId, seq, mUserId); 1027 } catch (RemoteException e) { 1028 throw new RuntimeException(e); 1029 } 1030 } 1031 } 1032 1033 /** 1034 * Returns the TvStreamConfig list of the given TV input. 1035 * 1036 * If you are using {@link Hardware} object from {@link 1037 * #acquireTvInputHardware}, you should get the list of available streams 1038 * from {@link HardwareCallback#onStreamConfigChanged} method, not from 1039 * here. This method is designed to be used with {@link #captureFrame} in 1040 * capture scenarios specifically and not suitable for any other use. 1041 * 1042 * @param inputId the id of the TV input. 1043 * @return List of {@link TvStreamConfig} which is available for capturing 1044 * of the given TV input. 1045 * @hide 1046 */ 1047 @SystemApi 1048 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId) { 1049 try { 1050 return mService.getAvailableTvStreamConfigList(inputId, mUserId); 1051 } catch (RemoteException e) { 1052 throw new RuntimeException(e); 1053 } 1054 } 1055 1056 /** 1057 * Take a snapshot of the given TV input into the provided Surface. 1058 * 1059 * @param inputId the id of the TV input. 1060 * @param surface the {@link Surface} to which the snapshot is captured. 1061 * @param config the {@link TvStreamConfig} which is used for capturing. 1062 * @return true when the {@link Surface} is ready to be captured. 1063 * @hide 1064 */ 1065 @SystemApi 1066 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config) { 1067 try { 1068 return mService.captureFrame(inputId, surface, config, mUserId); 1069 } catch (RemoteException e) { 1070 throw new RuntimeException(e); 1071 } 1072 } 1073 1074 /** 1075 * Returns true if there is only a single TV input session. 1076 * 1077 * @hide 1078 */ 1079 @SystemApi 1080 public boolean isSingleSessionActive() { 1081 try { 1082 return mService.isSingleSessionActive(mUserId); 1083 } catch (RemoteException e) { 1084 throw new RuntimeException(e); 1085 } 1086 } 1087 1088 /** 1089 * Returns a list of TvInputHardwareInfo objects representing available hardware. 1090 * 1091 * @hide 1092 */ 1093 @SystemApi 1094 public List<TvInputHardwareInfo> getHardwareList() { 1095 try { 1096 return mService.getHardwareList(); 1097 } catch (RemoteException e) { 1098 throw new RuntimeException(e); 1099 } 1100 } 1101 1102 /** 1103 * Returns acquired TvInputManager.Hardware object for given deviceId. 1104 * 1105 * If there are other Hardware object acquired for the same deviceId, calling this method will 1106 * preempt the previously acquired object and report {@link HardwareCallback#onReleased} to the 1107 * old object. 1108 * 1109 * @hide 1110 */ 1111 @SystemApi 1112 public Hardware acquireTvInputHardware(int deviceId, final HardwareCallback callback, 1113 TvInputInfo info) { 1114 try { 1115 return new Hardware( 1116 mService.acquireTvInputHardware(deviceId, new ITvInputHardwareCallback.Stub() { 1117 @Override 1118 public void onReleased() { 1119 callback.onReleased(); 1120 } 1121 1122 @Override 1123 public void onStreamConfigChanged(TvStreamConfig[] configs) { 1124 callback.onStreamConfigChanged(configs); 1125 } 1126 }, info, mUserId)); 1127 } catch (RemoteException e) { 1128 throw new RuntimeException(e); 1129 } 1130 } 1131 1132 /** 1133 * Releases previously acquired hardware object. 1134 * 1135 * @hide 1136 */ 1137 @SystemApi 1138 public void releaseTvInputHardware(int deviceId, Hardware hardware) { 1139 try { 1140 mService.releaseTvInputHardware(deviceId, hardware.getInterface(), mUserId); 1141 } catch (RemoteException e) { 1142 throw new RuntimeException(e); 1143 } 1144 } 1145 1146 /** 1147 * The Session provides the per-session functionality of TV inputs. 1148 * @hide 1149 */ 1150 @SystemApi 1151 public static final class Session { 1152 static final int DISPATCH_IN_PROGRESS = -1; 1153 static final int DISPATCH_NOT_HANDLED = 0; 1154 static final int DISPATCH_HANDLED = 1; 1155 1156 private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500; 1157 1158 private final ITvInputManager mService; 1159 private final int mUserId; 1160 private final int mSeq; 1161 1162 // For scheduling input event handling on the main thread. This also serves as a lock to 1163 // protect pending input events and the input channel. 1164 private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper()); 1165 1166 private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20); 1167 private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20); 1168 private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; 1169 1170 private IBinder mToken; 1171 private TvInputEventSender mSender; 1172 private InputChannel mChannel; 1173 1174 private final Object mTrackLock = new Object(); 1175 // @GuardedBy("mTrackLock") 1176 private final List<TvTrackInfo> mAudioTracks = new ArrayList<TvTrackInfo>(); 1177 // @GuardedBy("mTrackLock") 1178 private final List<TvTrackInfo> mVideoTracks = new ArrayList<TvTrackInfo>(); 1179 // @GuardedBy("mTrackLock") 1180 private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<TvTrackInfo>(); 1181 // @GuardedBy("mTrackLock") 1182 private String mSelectedAudioTrackId; 1183 // @GuardedBy("mTrackLock") 1184 private String mSelectedVideoTrackId; 1185 // @GuardedBy("mTrackLock") 1186 private String mSelectedSubtitleTrackId; 1187 // @GuardedBy("mTrackLock") 1188 private int mVideoWidth; 1189 // @GuardedBy("mTrackLock") 1190 private int mVideoHeight; 1191 1192 private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId, 1193 int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { 1194 mToken = token; 1195 mChannel = channel; 1196 mService = service; 1197 mUserId = userId; 1198 mSeq = seq; 1199 mSessionCallbackRecordMap = sessionCallbackRecordMap; 1200 } 1201 1202 /** 1203 * Releases this session. 1204 */ 1205 public void release() { 1206 if (mToken == null) { 1207 Log.w(TAG, "The session has been already released"); 1208 return; 1209 } 1210 try { 1211 mService.releaseSession(mToken, mUserId); 1212 } catch (RemoteException e) { 1213 throw new RuntimeException(e); 1214 } 1215 1216 releaseInternal(); 1217 } 1218 1219 /** 1220 * Sets this as the main session. The main session is a session whose corresponding TV 1221 * input determines the HDMI-CEC active source device. 1222 * 1223 * @see TvView#setMain 1224 */ 1225 void setMain() { 1226 if (mToken == null) { 1227 Log.w(TAG, "The session has been already released"); 1228 return; 1229 } 1230 try { 1231 mService.setMainSession(mToken, mUserId); 1232 } catch (RemoteException e) { 1233 throw new RuntimeException(e); 1234 } 1235 } 1236 1237 /** 1238 * Sets the {@link android.view.Surface} for this session. 1239 * 1240 * @param surface A {@link android.view.Surface} used to render video. 1241 */ 1242 public void setSurface(Surface surface) { 1243 if (mToken == null) { 1244 Log.w(TAG, "The session has been already released"); 1245 return; 1246 } 1247 // surface can be null. 1248 try { 1249 mService.setSurface(mToken, surface, mUserId); 1250 } catch (RemoteException e) { 1251 throw new RuntimeException(e); 1252 } 1253 } 1254 1255 /** 1256 * Notifies of any structural changes (format or size) of the {@link Surface} 1257 * passed by {@link #setSurface}. 1258 * 1259 * @param format The new PixelFormat of the {@link Surface}. 1260 * @param width The new width of the {@link Surface}. 1261 * @param height The new height of the {@link Surface}. 1262 * @hide 1263 */ 1264 @SystemApi 1265 public void dispatchSurfaceChanged(int format, int width, int height) { 1266 if (mToken == null) { 1267 Log.w(TAG, "The session has been already released"); 1268 return; 1269 } 1270 try { 1271 mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId); 1272 } catch (RemoteException e) { 1273 throw new RuntimeException(e); 1274 } 1275 } 1276 1277 /** 1278 * Sets the relative stream volume of this session to handle a change of audio focus. 1279 * 1280 * @param volume A volume value between 0.0f to 1.0f. 1281 * @throws IllegalArgumentException if the volume value is out of range. 1282 */ 1283 public void setStreamVolume(float volume) { 1284 if (mToken == null) { 1285 Log.w(TAG, "The session has been already released"); 1286 return; 1287 } 1288 try { 1289 if (volume < 0.0f || volume > 1.0f) { 1290 throw new IllegalArgumentException("volume should be between 0.0f and 1.0f"); 1291 } 1292 mService.setVolume(mToken, volume, mUserId); 1293 } catch (RemoteException e) { 1294 throw new RuntimeException(e); 1295 } 1296 } 1297 1298 /** 1299 * Tunes to a given channel. 1300 * 1301 * @param channelUri The URI of a channel. 1302 * @throws IllegalArgumentException if the argument is {@code null}. 1303 */ 1304 public void tune(Uri channelUri) { 1305 tune(channelUri, null); 1306 } 1307 1308 /** 1309 * Tunes to a given channel. 1310 * 1311 * @param channelUri The URI of a channel. 1312 * @param params A set of extra parameters which might be handled with this tune event. 1313 * @throws IllegalArgumentException if {@code channelUri} is {@code null}. 1314 * @hide 1315 */ 1316 @SystemApi 1317 public void tune(Uri channelUri, Bundle params) { 1318 if (channelUri == null) { 1319 throw new IllegalArgumentException("channelUri cannot be null"); 1320 } 1321 if (mToken == null) { 1322 Log.w(TAG, "The session has been already released"); 1323 return; 1324 } 1325 synchronized (mTrackLock) { 1326 mAudioTracks.clear(); 1327 mVideoTracks.clear(); 1328 mSubtitleTracks.clear(); 1329 mSelectedAudioTrackId = null; 1330 mSelectedVideoTrackId = null; 1331 mSelectedSubtitleTrackId = null; 1332 mVideoWidth = 0; 1333 mVideoHeight = 0; 1334 } 1335 try { 1336 mService.tune(mToken, channelUri, params, mUserId); 1337 } catch (RemoteException e) { 1338 throw new RuntimeException(e); 1339 } 1340 } 1341 1342 /** 1343 * Enables or disables the caption for this session. 1344 * 1345 * @param enabled {@code true} to enable, {@code false} to disable. 1346 */ 1347 public void setCaptionEnabled(boolean enabled) { 1348 if (mToken == null) { 1349 Log.w(TAG, "The session has been already released"); 1350 return; 1351 } 1352 try { 1353 mService.setCaptionEnabled(mToken, enabled, mUserId); 1354 } catch (RemoteException e) { 1355 throw new RuntimeException(e); 1356 } 1357 } 1358 1359 /** 1360 * Selects a track. 1361 * 1362 * @param type The type of the track to select. The type can be 1363 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or 1364 * {@link TvTrackInfo#TYPE_SUBTITLE}. 1365 * @param trackId The ID of the track to select. When {@code null}, the currently selected 1366 * track of the given type will be unselected. 1367 * @see #getTracks 1368 */ 1369 public void selectTrack(int type, String trackId) { 1370 synchronized (mTrackLock) { 1371 if (type == TvTrackInfo.TYPE_AUDIO) { 1372 if (trackId != null && !containsTrack(mAudioTracks, trackId)) { 1373 Log.w(TAG, "Invalid audio trackId: " + trackId); 1374 return; 1375 } 1376 } else if (type == TvTrackInfo.TYPE_VIDEO) { 1377 if (trackId != null && !containsTrack(mVideoTracks, trackId)) { 1378 Log.w(TAG, "Invalid video trackId: " + trackId); 1379 return; 1380 } 1381 } else if (type == TvTrackInfo.TYPE_SUBTITLE) { 1382 if (trackId != null && !containsTrack(mSubtitleTracks, trackId)) { 1383 Log.w(TAG, "Invalid subtitle trackId: " + trackId); 1384 return; 1385 } 1386 } else { 1387 throw new IllegalArgumentException("invalid type: " + type); 1388 } 1389 } 1390 if (mToken == null) { 1391 Log.w(TAG, "The session has been already released"); 1392 return; 1393 } 1394 try { 1395 mService.selectTrack(mToken, type, trackId, mUserId); 1396 } catch (RemoteException e) { 1397 throw new RuntimeException(e); 1398 } 1399 } 1400 1401 private boolean containsTrack(List<TvTrackInfo> tracks, String trackId) { 1402 for (TvTrackInfo track : tracks) { 1403 if (track.getId().equals(trackId)) { 1404 return true; 1405 } 1406 } 1407 return false; 1408 } 1409 1410 /** 1411 * Returns the list of tracks for a given type. Returns {@code null} if the information is 1412 * not available. 1413 * 1414 * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO}, 1415 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}. 1416 * @return the list of tracks for the given type. 1417 */ 1418 public List<TvTrackInfo> getTracks(int type) { 1419 synchronized (mTrackLock) { 1420 if (type == TvTrackInfo.TYPE_AUDIO) { 1421 if (mAudioTracks == null) { 1422 return null; 1423 } 1424 return new ArrayList<TvTrackInfo>(mAudioTracks); 1425 } else if (type == TvTrackInfo.TYPE_VIDEO) { 1426 if (mVideoTracks == null) { 1427 return null; 1428 } 1429 return new ArrayList<TvTrackInfo>(mVideoTracks); 1430 } else if (type == TvTrackInfo.TYPE_SUBTITLE) { 1431 if (mSubtitleTracks == null) { 1432 return null; 1433 } 1434 return new ArrayList<TvTrackInfo>(mSubtitleTracks); 1435 } 1436 } 1437 throw new IllegalArgumentException("invalid type: " + type); 1438 } 1439 1440 /** 1441 * Returns the selected track for a given type. Returns {@code null} if the information is 1442 * not available or any of the tracks for the given type is not selected. 1443 * 1444 * @return the ID of the selected track. 1445 * @see #selectTrack 1446 */ 1447 public String getSelectedTrack(int type) { 1448 synchronized (mTrackLock) { 1449 if (type == TvTrackInfo.TYPE_AUDIO) { 1450 return mSelectedAudioTrackId; 1451 } else if (type == TvTrackInfo.TYPE_VIDEO) { 1452 return mSelectedVideoTrackId; 1453 } else if (type == TvTrackInfo.TYPE_SUBTITLE) { 1454 return mSelectedSubtitleTrackId; 1455 } 1456 } 1457 throw new IllegalArgumentException("invalid type: " + type); 1458 } 1459 1460 /** 1461 * Responds to onTracksChanged() and updates the internal track information. Returns true if 1462 * there is an update. 1463 */ 1464 boolean updateTracks(List<TvTrackInfo> tracks) { 1465 synchronized (mTrackLock) { 1466 mAudioTracks.clear(); 1467 mVideoTracks.clear(); 1468 mSubtitleTracks.clear(); 1469 for (TvTrackInfo track : tracks) { 1470 if (track.getType() == TvTrackInfo.TYPE_AUDIO) { 1471 mAudioTracks.add(track); 1472 } else if (track.getType() == TvTrackInfo.TYPE_VIDEO) { 1473 mVideoTracks.add(track); 1474 } else if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) { 1475 mSubtitleTracks.add(track); 1476 } 1477 } 1478 return !mAudioTracks.isEmpty() || !mVideoTracks.isEmpty() 1479 || !mSubtitleTracks.isEmpty(); 1480 } 1481 } 1482 1483 /** 1484 * Responds to onTrackSelected() and updates the internal track selection information. 1485 * Returns true if there is an update. 1486 */ 1487 boolean updateTrackSelection(int type, String trackId) { 1488 synchronized (mTrackLock) { 1489 if (type == TvTrackInfo.TYPE_AUDIO && trackId != mSelectedAudioTrackId) { 1490 mSelectedAudioTrackId = trackId; 1491 return true; 1492 } else if (type == TvTrackInfo.TYPE_VIDEO && trackId != mSelectedVideoTrackId) { 1493 mSelectedVideoTrackId = trackId; 1494 return true; 1495 } else if (type == TvTrackInfo.TYPE_SUBTITLE 1496 && trackId != mSelectedSubtitleTrackId) { 1497 mSelectedSubtitleTrackId = trackId; 1498 return true; 1499 } 1500 } 1501 return false; 1502 } 1503 1504 /** 1505 * Returns the new/updated video track that contains new video size information. Returns 1506 * null if there is no video track to notify. Subsequent calls of this method results in a 1507 * non-null video track returned only by the first call and null returned by following 1508 * calls. The caller should immediately notify of the video size change upon receiving the 1509 * track. 1510 */ 1511 TvTrackInfo getVideoTrackToNotify() { 1512 synchronized (mTrackLock) { 1513 if (!mVideoTracks.isEmpty() && mSelectedVideoTrackId != null) { 1514 for (TvTrackInfo track : mVideoTracks) { 1515 if (track.getId().equals(mSelectedVideoTrackId)) { 1516 int videoWidth = track.getVideoWidth(); 1517 int videoHeight = track.getVideoHeight(); 1518 if (mVideoWidth != videoWidth || mVideoHeight != videoHeight) { 1519 mVideoWidth = videoWidth; 1520 mVideoHeight = videoHeight; 1521 return track; 1522 } 1523 } 1524 } 1525 } 1526 } 1527 return null; 1528 } 1529 1530 /** 1531 * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle) 1532 * TvInputService.Session.appPrivateCommand()} on the current TvView. 1533 * 1534 * @param action Name of the command to be performed. This <em>must</em> be a scoped name, 1535 * i.e. prefixed with a package name you own, so that different developers will 1536 * not create conflicting commands. 1537 * @param data Any data to include with the command. 1538 * @hide 1539 */ 1540 @SystemApi 1541 public void sendAppPrivateCommand(String action, Bundle data) { 1542 if (mToken == null) { 1543 Log.w(TAG, "The session has been already released"); 1544 return; 1545 } 1546 try { 1547 mService.sendAppPrivateCommand(mToken, action, data, mUserId); 1548 } catch (RemoteException e) { 1549 throw new RuntimeException(e); 1550 } 1551 } 1552 1553 /** 1554 * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView} 1555 * should be called whenever the layout of its containing view is changed. 1556 * {@link #removeOverlayView()} should be called to remove the overlay view. 1557 * Since a session can have only one overlay view, this method should be called only once 1558 * or it can be called again after calling {@link #removeOverlayView()}. 1559 * 1560 * @param view A view playing TV. 1561 * @param frame A position of the overlay view. 1562 * @throws IllegalArgumentException if any of the arguments is {@code null}. 1563 * @throws IllegalStateException if {@code view} is not attached to a window. 1564 */ 1565 void createOverlayView(View view, Rect frame) { 1566 if (view == null) { 1567 throw new IllegalArgumentException("view cannot be null"); 1568 } 1569 if (frame == null) { 1570 throw new IllegalArgumentException("frame cannot be null"); 1571 } 1572 if (view.getWindowToken() == null) { 1573 throw new IllegalStateException("view must be attached to a window"); 1574 } 1575 if (mToken == null) { 1576 Log.w(TAG, "The session has been already released"); 1577 return; 1578 } 1579 try { 1580 mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId); 1581 } catch (RemoteException e) { 1582 throw new RuntimeException(e); 1583 } 1584 } 1585 1586 /** 1587 * Relayouts the current overlay view. 1588 * 1589 * @param frame A new position of the overlay view. 1590 * @throws IllegalArgumentException if the arguments is {@code null}. 1591 */ 1592 void relayoutOverlayView(Rect frame) { 1593 if (frame == null) { 1594 throw new IllegalArgumentException("frame cannot be null"); 1595 } 1596 if (mToken == null) { 1597 Log.w(TAG, "The session has been already released"); 1598 return; 1599 } 1600 try { 1601 mService.relayoutOverlayView(mToken, frame, mUserId); 1602 } catch (RemoteException e) { 1603 throw new RuntimeException(e); 1604 } 1605 } 1606 1607 /** 1608 * Removes the current overlay view. 1609 */ 1610 void removeOverlayView() { 1611 if (mToken == null) { 1612 Log.w(TAG, "The session has been already released"); 1613 return; 1614 } 1615 try { 1616 mService.removeOverlayView(mToken, mUserId); 1617 } catch (RemoteException e) { 1618 throw new RuntimeException(e); 1619 } 1620 } 1621 1622 /** 1623 * Requests to unblock content blocked by parental controls. 1624 */ 1625 void requestUnblockContent(TvContentRating unblockedRating) { 1626 if (mToken == null) { 1627 Log.w(TAG, "The session has been already released"); 1628 return; 1629 } 1630 if (unblockedRating == null) { 1631 throw new IllegalArgumentException("unblockedRating cannot be null"); 1632 } 1633 try { 1634 mService.requestUnblockContent(mToken, unblockedRating.flattenToString(), mUserId); 1635 } catch (RemoteException e) { 1636 throw new RuntimeException(e); 1637 } 1638 } 1639 1640 /** 1641 * Dispatches an input event to this session. 1642 * 1643 * @param event An {@link InputEvent} to dispatch. 1644 * @param token A token used to identify the input event later in the callback. 1645 * @param callback A callback used to receive the dispatch result. 1646 * @param handler A {@link Handler} that the dispatch result will be delivered to. 1647 * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns 1648 * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns 1649 * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will 1650 * be invoked later. 1651 * @throws IllegalArgumentException if any of the necessary arguments is {@code null}. 1652 * @hide 1653 */ 1654 public int dispatchInputEvent(InputEvent event, Object token, 1655 FinishedInputEventCallback callback, Handler handler) { 1656 if (event == null) { 1657 throw new IllegalArgumentException("event cannot be null"); 1658 } 1659 if (callback != null && handler == null) { 1660 throw new IllegalArgumentException("handler cannot be null"); 1661 } 1662 synchronized (mHandler) { 1663 if (mChannel == null) { 1664 return DISPATCH_NOT_HANDLED; 1665 } 1666 PendingEvent p = obtainPendingEventLocked(event, token, callback, handler); 1667 if (Looper.myLooper() == Looper.getMainLooper()) { 1668 // Already running on the main thread so we can send the event immediately. 1669 return sendInputEventOnMainLooperLocked(p); 1670 } 1671 1672 // Post the event to the main thread. 1673 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p); 1674 msg.setAsynchronous(true); 1675 mHandler.sendMessage(msg); 1676 return DISPATCH_IN_PROGRESS; 1677 } 1678 } 1679 1680 /** 1681 * Callback that is invoked when an input event that was dispatched to this session has been 1682 * finished. 1683 * 1684 * @hide 1685 */ 1686 public interface FinishedInputEventCallback { 1687 /** 1688 * Called when the dispatched input event is finished. 1689 * 1690 * @param token A token passed to {@link #dispatchInputEvent}. 1691 * @param handled {@code true} if the dispatched input event was handled properly. 1692 * {@code false} otherwise. 1693 */ 1694 public void onFinishedInputEvent(Object token, boolean handled); 1695 } 1696 1697 // Must be called on the main looper 1698 private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) { 1699 synchronized (mHandler) { 1700 int result = sendInputEventOnMainLooperLocked(p); 1701 if (result == DISPATCH_IN_PROGRESS) { 1702 return; 1703 } 1704 } 1705 1706 invokeFinishedInputEventCallback(p, false); 1707 } 1708 1709 private int sendInputEventOnMainLooperLocked(PendingEvent p) { 1710 if (mChannel != null) { 1711 if (mSender == null) { 1712 mSender = new TvInputEventSender(mChannel, mHandler.getLooper()); 1713 } 1714 1715 final InputEvent event = p.mEvent; 1716 final int seq = event.getSequenceNumber(); 1717 if (mSender.sendInputEvent(seq, event)) { 1718 mPendingEvents.put(seq, p); 1719 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); 1720 msg.setAsynchronous(true); 1721 mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT); 1722 return DISPATCH_IN_PROGRESS; 1723 } 1724 1725 Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:" 1726 + event); 1727 } 1728 return DISPATCH_NOT_HANDLED; 1729 } 1730 1731 void finishedInputEvent(int seq, boolean handled, boolean timeout) { 1732 final PendingEvent p; 1733 synchronized (mHandler) { 1734 int index = mPendingEvents.indexOfKey(seq); 1735 if (index < 0) { 1736 return; // spurious, event already finished or timed out 1737 } 1738 1739 p = mPendingEvents.valueAt(index); 1740 mPendingEvents.removeAt(index); 1741 1742 if (timeout) { 1743 Log.w(TAG, "Timeout waiting for seesion to handle input event after " 1744 + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken); 1745 } else { 1746 mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); 1747 } 1748 } 1749 1750 invokeFinishedInputEventCallback(p, handled); 1751 } 1752 1753 // Assumes the event has already been removed from the queue. 1754 void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { 1755 p.mHandled = handled; 1756 if (p.mEventHandler.getLooper().isCurrentThread()) { 1757 // Already running on the callback handler thread so we can send the callback 1758 // immediately. 1759 p.run(); 1760 } else { 1761 // Post the event to the callback handler thread. 1762 // In this case, the callback will be responsible for recycling the event. 1763 Message msg = Message.obtain(p.mEventHandler, p); 1764 msg.setAsynchronous(true); 1765 msg.sendToTarget(); 1766 } 1767 } 1768 1769 private void flushPendingEventsLocked() { 1770 mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT); 1771 1772 final int count = mPendingEvents.size(); 1773 for (int i = 0; i < count; i++) { 1774 int seq = mPendingEvents.keyAt(i); 1775 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0); 1776 msg.setAsynchronous(true); 1777 msg.sendToTarget(); 1778 } 1779 } 1780 1781 private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, 1782 FinishedInputEventCallback callback, Handler handler) { 1783 PendingEvent p = mPendingEventPool.acquire(); 1784 if (p == null) { 1785 p = new PendingEvent(); 1786 } 1787 p.mEvent = event; 1788 p.mEventToken = token; 1789 p.mCallback = callback; 1790 p.mEventHandler = handler; 1791 return p; 1792 } 1793 1794 private void recyclePendingEventLocked(PendingEvent p) { 1795 p.recycle(); 1796 mPendingEventPool.release(p); 1797 } 1798 1799 IBinder getToken() { 1800 return mToken; 1801 } 1802 1803 private void releaseInternal() { 1804 mToken = null; 1805 synchronized (mHandler) { 1806 if (mChannel != null) { 1807 if (mSender != null) { 1808 flushPendingEventsLocked(); 1809 mSender.dispose(); 1810 mSender = null; 1811 } 1812 mChannel.dispose(); 1813 mChannel = null; 1814 } 1815 } 1816 synchronized (mSessionCallbackRecordMap) { 1817 mSessionCallbackRecordMap.remove(mSeq); 1818 } 1819 } 1820 1821 private final class InputEventHandler extends Handler { 1822 public static final int MSG_SEND_INPUT_EVENT = 1; 1823 public static final int MSG_TIMEOUT_INPUT_EVENT = 2; 1824 public static final int MSG_FLUSH_INPUT_EVENT = 3; 1825 1826 InputEventHandler(Looper looper) { 1827 super(looper, null, true); 1828 } 1829 1830 @Override 1831 public void handleMessage(Message msg) { 1832 switch (msg.what) { 1833 case MSG_SEND_INPUT_EVENT: { 1834 sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj); 1835 return; 1836 } 1837 case MSG_TIMEOUT_INPUT_EVENT: { 1838 finishedInputEvent(msg.arg1, false, true); 1839 return; 1840 } 1841 case MSG_FLUSH_INPUT_EVENT: { 1842 finishedInputEvent(msg.arg1, false, false); 1843 return; 1844 } 1845 } 1846 } 1847 } 1848 1849 private final class TvInputEventSender extends InputEventSender { 1850 public TvInputEventSender(InputChannel inputChannel, Looper looper) { 1851 super(inputChannel, looper); 1852 } 1853 1854 @Override 1855 public void onInputEventFinished(int seq, boolean handled) { 1856 finishedInputEvent(seq, handled, false); 1857 } 1858 } 1859 1860 private final class PendingEvent implements Runnable { 1861 public InputEvent mEvent; 1862 public Object mEventToken; 1863 public FinishedInputEventCallback mCallback; 1864 public Handler mEventHandler; 1865 public boolean mHandled; 1866 1867 public void recycle() { 1868 mEvent = null; 1869 mEventToken = null; 1870 mCallback = null; 1871 mEventHandler = null; 1872 mHandled = false; 1873 } 1874 1875 @Override 1876 public void run() { 1877 mCallback.onFinishedInputEvent(mEventToken, mHandled); 1878 1879 synchronized (mEventHandler) { 1880 recyclePendingEventLocked(this); 1881 } 1882 } 1883 } 1884 } 1885 1886 /** 1887 * The Hardware provides the per-hardware functionality of TV hardware. 1888 * 1889 * TV hardware is physical hardware attached to the Android device; for example, HDMI ports, 1890 * Component/Composite ports, etc. Specifically, logical devices such as HDMI CEC logical 1891 * devices don't fall into this category. 1892 * 1893 * @hide 1894 */ 1895 @SystemApi 1896 public final static class Hardware { 1897 private final ITvInputHardware mInterface; 1898 1899 private Hardware(ITvInputHardware hardwareInterface) { 1900 mInterface = hardwareInterface; 1901 } 1902 1903 private ITvInputHardware getInterface() { 1904 return mInterface; 1905 } 1906 1907 public boolean setSurface(Surface surface, TvStreamConfig config) { 1908 try { 1909 return mInterface.setSurface(surface, config); 1910 } catch (RemoteException e) { 1911 throw new RuntimeException(e); 1912 } 1913 } 1914 1915 public void setStreamVolume(float volume) { 1916 try { 1917 mInterface.setStreamVolume(volume); 1918 } catch (RemoteException e) { 1919 throw new RuntimeException(e); 1920 } 1921 } 1922 1923 public boolean dispatchKeyEventToHdmi(KeyEvent event) { 1924 try { 1925 return mInterface.dispatchKeyEventToHdmi(event); 1926 } catch (RemoteException e) { 1927 throw new RuntimeException(e); 1928 } 1929 } 1930 1931 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, 1932 int channelMask, int format) { 1933 try { 1934 mInterface.overrideAudioSink(audioType, audioAddress, samplingRate, channelMask, 1935 format); 1936 } catch (RemoteException e) { 1937 throw new RuntimeException(e); 1938 } 1939 } 1940 } 1941 } 1942