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