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