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