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