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