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