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