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