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