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