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