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