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