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