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