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