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