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