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