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