TvInputManager.java revision ff04ae757a5542d2d5633e75b7adacc4fce1ce7e
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
17package android.media.tv;
18
19import android.annotation.SystemApi;
20import android.graphics.Rect;
21import android.net.Uri;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.IBinder;
25import android.os.Looper;
26import android.os.Message;
27import android.os.RemoteException;
28import android.util.ArrayMap;
29import android.util.Log;
30import android.util.Pools.Pool;
31import android.util.Pools.SimplePool;
32import android.util.SparseArray;
33import android.view.InputChannel;
34import android.view.InputEvent;
35import android.view.InputEventSender;
36import android.view.Surface;
37import android.view.View;
38
39import java.util.ArrayList;
40import java.util.Iterator;
41import java.util.LinkedList;
42import java.util.List;
43import java.util.Map;
44
45/**
46 * Central system API to the overall TV input framework (TIF) architecture, which arbitrates
47 * interaction between applications and the selected TV inputs.
48 */
49public final class TvInputManager {
50    private static final String TAG = "TvInputManager";
51
52    static final int VIDEO_UNAVAILABLE_REASON_START = 0;
53    static final int VIDEO_UNAVAILABLE_REASON_END = 3;
54
55    /**
56     * A generic reason. Video is not available due to an unspecified error.
57     */
58    public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = VIDEO_UNAVAILABLE_REASON_START;
59    /**
60     * Video is not available because the TV input is in the middle of tuning to a new channel.
61     */
62    public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1;
63    /**
64     * Video is not available due to the weak TV signal.
65     */
66    public static final int VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2;
67    /**
68     * Video is not available because the TV input stopped the playback temporarily to buffer more
69     * data.
70     */
71    public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = VIDEO_UNAVAILABLE_REASON_END;
72
73    /**
74     * The TV input is connected.
75     * <p>
76     * State for {@link #getInputState} and {@link
77     * TvInputManager.TvInputListener#onInputStateChanged}.
78     * </p>
79     */
80    public static final int INPUT_STATE_CONNECTED = 0;
81    /**
82     * The TV input is connected but in standby mode. It would take a while until it becomes
83     * fully ready.
84     * <p>
85     * State for {@link #getInputState} and {@link
86     * TvInputManager.TvInputListener#onInputStateChanged}.
87     * </p>
88     */
89    public static final int INPUT_STATE_CONNECTED_STANDBY = 1;
90    /**
91     * The TV input is disconnected.
92     * <p>
93     * State for {@link #getInputState} and {@link
94     * TvInputManager.TvInputListener#onInputStateChanged}.
95     * </p>
96     */
97    public static final int INPUT_STATE_DISCONNECTED = 2;
98
99    /**
100     * Broadcast intent action when the user blocked content ratings change. For use with the
101     * {@link #isRatingBlocked}.
102     */
103    public static final String ACTION_BLOCKED_RATINGS_CHANGED =
104            "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
105
106    /**
107     * Broadcast intent action when the parental controls enabled state changes. For use with the
108     * {@link #isParentalControlsEnabled}.
109     */
110    public static final String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED =
111            "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
112
113    private final ITvInputManager mService;
114
115    private final Object mLock = new Object();
116
117    // @GuardedBy(mLock)
118    private final List<TvInputListenerRecord> mTvInputListenerRecordsList =
119            new LinkedList<TvInputListenerRecord>();
120
121    // A mapping from TV input ID to the state of corresponding input.
122    // @GuardedBy(mLock)
123    private final Map<String, Integer> mStateMap = new ArrayMap<String, Integer>();
124
125    // A mapping from the sequence number of a session to its SessionCallbackRecord.
126    private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
127            new SparseArray<SessionCallbackRecord>();
128
129    // A sequence number for the next session to be created. Should be protected by a lock
130    // {@code mSessionCallbackRecordMap}.
131    private int mNextSeq;
132
133    private final ITvInputClient mClient;
134
135    private final ITvInputManagerCallback mCallback;
136
137    private final int mUserId;
138
139    /**
140     * Interface used to receive the created session.
141     * @hide
142     */
143    @SystemApi
144    public abstract static class SessionCallback {
145        /**
146         * This is called after {@link TvInputManager#createSession} has been processed.
147         *
148         * @param session A {@link TvInputManager.Session} instance created. This can be
149         *            {@code null} if the creation request failed.
150         */
151        public void onSessionCreated(Session session) {
152        }
153
154        /**
155         * This is called when {@link TvInputManager.Session} is released.
156         * This typically happens when the process hosting the session has crashed or been killed.
157         *
158         * @param session A {@link TvInputManager.Session} instance released.
159         */
160        public void onSessionReleased(Session session) {
161        }
162
163        /**
164         * This is called when the channel of this session is changed by the underlying TV input
165         * with out any {@link TvInputManager.Session#tune(Uri)} request.
166         *
167         * @param session A {@link TvInputManager.Session} associated with this callback.
168         * @param channelUri The URI of a channel.
169         */
170        public void onChannelRetuned(Session session, Uri channelUri) {
171        }
172
173        /**
174         * This is called when the track information of the session has been changed.
175         *
176         * @param session A {@link TvInputManager.Session} associated with this callback.
177         * @param tracks A list which includes track information.
178         */
179        public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
180        }
181
182        /**
183         * This is called when a track for a given type is selected.
184         *
185         * @param session A {@link TvInputManager.Session} associated with this callback
186         * @param type The type of the selected track. The type can be
187         *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
188         *            {@link TvTrackInfo#TYPE_SUBTITLE}.
189         * @param trackId The ID of the selected track. When {@code null} the currently selected
190         *            track for a given type should be unselected.
191         */
192        public void onTrackSelected(Session session, int type, String trackId) {
193        }
194
195        /**
196         * This is called when the video is available, so the TV input starts the playback.
197         *
198         * @param session A {@link TvInputManager.Session} associated with this callback.
199         */
200        public void onVideoAvailable(Session session) {
201        }
202
203        /**
204         * This is called when the video is not available, so the TV input stops the playback.
205         *
206         * @param session A {@link TvInputManager.Session} associated with this callback
207         * @param reason The reason why the TV input stopped the playback:
208         * <ul>
209         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
210         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
211         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
212         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
213         * </ul>
214         */
215        public void onVideoUnavailable(Session session, int reason) {
216        }
217
218        /**
219         * This is called when the current program content turns out to be allowed to watch since
220         * its content rating is not blocked by parental controls.
221         *
222         * @param session A {@link TvInputManager.Session} associated with this callback
223         */
224        public void onContentAllowed(Session session) {
225        }
226
227        /**
228         * This is called when the current program content turns out to be not allowed to watch
229         * since its content rating is blocked by parental controls.
230         *
231         * @param session A {@link TvInputManager.Session} associated with this callback
232         * @param rating The content ration of the blocked program.
233         */
234        public void onContentBlocked(Session session, TvContentRating rating) {
235        }
236
237        /**
238         * This is called when {@link TvInputService.Session#layoutSurface} is called to
239         * change the layout of surface.
240         *
241         * @param session A {@link TvInputManager.Session} associated with this callback
242         * @param l Left position.
243         * @param t Top position.
244         * @param r Right position.
245         * @param b Bottom position.
246         * @hide
247         */
248        @SystemApi
249        public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
250        }
251
252        /**
253         * This is called when a custom event has been sent from this session.
254         *
255         * @param session A {@link TvInputManager.Session} associated with this callback
256         * @param eventType The type of the event.
257         * @param eventArgs Optional arguments of the event.
258         * @hide
259         */
260        @SystemApi
261        public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
262        }
263    }
264
265    private static final class SessionCallbackRecord {
266        private final SessionCallback mSessionCallback;
267        private final Handler mHandler;
268        private Session mSession;
269
270        public SessionCallbackRecord(SessionCallback sessionCallback,
271                Handler handler) {
272            mSessionCallback = sessionCallback;
273            mHandler = handler;
274        }
275
276        public void postSessionCreated(final Session session) {
277            mSession = session;
278            mHandler.post(new Runnable() {
279                @Override
280                public void run() {
281                    mSessionCallback.onSessionCreated(session);
282                }
283            });
284        }
285
286        public void postSessionReleased() {
287            mHandler.post(new Runnable() {
288                @Override
289                public void run() {
290                    mSessionCallback.onSessionReleased(mSession);
291                }
292            });
293        }
294
295        public void postChannelRetuned(final Uri channelUri) {
296            mHandler.post(new Runnable() {
297                @Override
298                public void run() {
299                    mSessionCallback.onChannelRetuned(mSession, channelUri);
300                }
301            });
302        }
303
304        public void postTracksChanged(final List<TvTrackInfo> tracks) {
305            mHandler.post(new Runnable() {
306                @Override
307                public void run() {
308                    mSession.mAudioTracks.clear();
309                    mSession.mVideoTracks.clear();
310                    mSession.mSubtitleTracks.clear();
311                    for (TvTrackInfo track : tracks) {
312                        if (track.getType() == TvTrackInfo.TYPE_AUDIO) {
313                            mSession.mAudioTracks.add(track);
314                        } else if (track.getType() == TvTrackInfo.TYPE_VIDEO) {
315                            mSession.mVideoTracks.add(track);
316                        } else if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) {
317                            mSession.mSubtitleTracks.add(track);
318                        } else {
319                            // Silently ignore.
320                        }
321                    }
322                    mSessionCallback.onTracksChanged(mSession, tracks);
323                }
324            });
325        }
326
327        public void postTrackSelected(final int type, final String trackId) {
328            mHandler.post(new Runnable() {
329                @Override
330                public void run() {
331                    if (type == TvTrackInfo.TYPE_AUDIO) {
332                        mSession.mSelectedAudioTrackId = trackId;
333                    } else if (type == TvTrackInfo.TYPE_VIDEO) {
334                        mSession.mSelectedVideoTrackId = trackId;
335                    } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
336                        mSession.mSelectedSubtitleTrackId = trackId;
337                    } else {
338                        // Silently ignore.
339                        return;
340                    }
341                    mSessionCallback.onTrackSelected(mSession, type, trackId);
342                }
343            });
344        }
345
346        public void postVideoAvailable() {
347            mHandler.post(new Runnable() {
348                @Override
349                public void run() {
350                    mSessionCallback.onVideoAvailable(mSession);
351                }
352            });
353        }
354
355        public void postVideoUnavailable(final int reason) {
356            mHandler.post(new Runnable() {
357                @Override
358                public void run() {
359                    mSessionCallback.onVideoUnavailable(mSession, reason);
360                }
361            });
362        }
363
364        public void postContentAllowed() {
365            mHandler.post(new Runnable() {
366                @Override
367                public void run() {
368                    mSessionCallback.onContentAllowed(mSession);
369                }
370            });
371        }
372
373        public void postContentBlocked(final TvContentRating rating) {
374            mHandler.post(new Runnable() {
375                @Override
376                public void run() {
377                    mSessionCallback.onContentBlocked(mSession, rating);
378                }
379            });
380        }
381
382        public void postLayoutSurface(final int left, final int top, final int right,
383                final int bottom) {
384            mHandler.post(new Runnable() {
385                @Override
386                public void run() {
387                    mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
388                }
389            });
390        }
391
392        public void postSessionEvent(final String eventType, final Bundle eventArgs) {
393            mHandler.post(new Runnable() {
394                @Override
395                public void run() {
396                    mSessionCallback.onSessionEvent(mSession, eventType, eventArgs);
397                }
398            });
399        }
400    }
401
402    /**
403     * Interface used to monitor status of the TV input.
404     */
405    public abstract static class TvInputListener {
406        /**
407         * This is called when the state of a given TV input is changed.
408         *
409         * @param inputId The id of the TV input.
410         * @param state State of the TV input. The value is one of the following:
411         * <ul>
412         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
413         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
414         * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED}
415         * </ul>
416         */
417        public void onInputStateChanged(String inputId, int state) {
418        }
419
420        /**
421         * This is called when a TV input is added.
422         *
423         * @param inputId The id of the TV input.
424         */
425        public void onInputAdded(String inputId) {
426        }
427
428        /**
429         * This is called when a TV input is removed.
430         *
431         * @param inputId The id of the TV input.
432         */
433        public void onInputRemoved(String inputId) {
434        }
435    }
436
437    private static final class TvInputListenerRecord {
438        private final TvInputListener mListener;
439        private final Handler mHandler;
440
441        public TvInputListenerRecord(TvInputListener listener, Handler handler) {
442            mListener = listener;
443            mHandler = handler;
444        }
445
446        public TvInputListener getListener() {
447            return mListener;
448        }
449
450        public void postInputStateChanged(final String inputId, final int state) {
451            mHandler.post(new Runnable() {
452                @Override
453                public void run() {
454                    mListener.onInputStateChanged(inputId, state);
455                }
456            });
457        }
458
459        public void postInputAdded(final String inputId) {
460            mHandler.post(new Runnable() {
461                @Override
462                public void run() {
463                    mListener.onInputAdded(inputId);
464                }
465            });
466        }
467
468        public void postInputRemoved(final String inputId) {
469            mHandler.post(new Runnable() {
470                @Override
471                public void run() {
472                    mListener.onInputRemoved(inputId);
473                }
474            });
475        }
476    }
477
478    /**
479     * @hide
480     */
481    public TvInputManager(ITvInputManager service, int userId) {
482        mService = service;
483        mUserId = userId;
484        mClient = new ITvInputClient.Stub() {
485            @Override
486            public void onSessionCreated(String inputId, IBinder token, InputChannel channel,
487                    int seq) {
488                synchronized (mSessionCallbackRecordMap) {
489                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
490                    if (record == null) {
491                        Log.e(TAG, "Callback not found for " + token);
492                        return;
493                    }
494                    Session session = null;
495                    if (token != null) {
496                        session = new Session(token, channel, mService, mUserId, seq,
497                                mSessionCallbackRecordMap);
498                    }
499                    record.postSessionCreated(session);
500                }
501            }
502
503            @Override
504            public void onSessionReleased(int seq) {
505                synchronized (mSessionCallbackRecordMap) {
506                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
507                    mSessionCallbackRecordMap.delete(seq);
508                    if (record == null) {
509                        Log.e(TAG, "Callback not found for seq:" + seq);
510                        return;
511                    }
512                    record.mSession.releaseInternal();
513                    record.postSessionReleased();
514                }
515            }
516
517            @Override
518            public void onChannelRetuned(Uri channelUri, int seq) {
519                synchronized (mSessionCallbackRecordMap) {
520                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
521                    if (record == null) {
522                        Log.e(TAG, "Callback not found for seq " + seq);
523                        return;
524                    }
525                    record.postChannelRetuned(channelUri);
526                }
527            }
528
529            @Override
530            public void onTracksChanged(List<TvTrackInfo> tracks, int seq) {
531                synchronized (mSessionCallbackRecordMap) {
532                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
533                    if (record == null) {
534                        Log.e(TAG, "Callback not found for seq " + seq);
535                        return;
536                    }
537                    record.postTracksChanged(tracks);
538                }
539            }
540
541            @Override
542            public void onTrackSelected(int type, String trackId, int seq) {
543                synchronized (mSessionCallbackRecordMap) {
544                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
545                    if (record == null) {
546                        Log.e(TAG, "Callback not found for seq " + seq);
547                        return;
548                    }
549                    record.postTrackSelected(type, trackId);
550                }
551            }
552
553            @Override
554            public void onVideoAvailable(int seq) {
555                synchronized (mSessionCallbackRecordMap) {
556                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
557                    if (record == null) {
558                        Log.e(TAG, "Callback not found for seq " + seq);
559                        return;
560                    }
561                    record.postVideoAvailable();
562                }
563            }
564
565            @Override
566            public void onVideoUnavailable(int reason, int seq) {
567                synchronized (mSessionCallbackRecordMap) {
568                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
569                    if (record == null) {
570                        Log.e(TAG, "Callback not found for seq " + seq);
571                        return;
572                    }
573                    record.postVideoUnavailable(reason);
574                }
575            }
576
577            @Override
578            public void onContentAllowed(int seq) {
579                synchronized (mSessionCallbackRecordMap) {
580                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
581                    if (record == null) {
582                        Log.e(TAG, "Callback not found for seq " + seq);
583                        return;
584                    }
585                    record.postContentAllowed();
586                }
587            }
588
589            @Override
590            public void onContentBlocked(String rating, int seq) {
591                synchronized (mSessionCallbackRecordMap) {
592                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
593                    if (record == null) {
594                        Log.e(TAG, "Callback not found for seq " + seq);
595                        return;
596                    }
597                    record.postContentBlocked(TvContentRating.unflattenFromString(rating));
598                }
599            }
600
601            @Override
602            public void onLayoutSurface(int left, int top, int right, int bottom, 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.postLayoutSurface(left, top, right, bottom);
610                }
611            }
612
613            @Override
614            public void onSessionEvent(String eventType, Bundle eventArgs, 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                    record.postSessionEvent(eventType, eventArgs);
622                }
623            }
624        };
625        mCallback = new ITvInputManagerCallback.Stub() {
626            @Override
627            public void onInputStateChanged(String inputId, int state) {
628                synchronized (mLock) {
629                    mStateMap.put(inputId, state);
630                    for (TvInputListenerRecord record : mTvInputListenerRecordsList) {
631                        record.postInputStateChanged(inputId, state);
632                    }
633                }
634            }
635
636            @Override
637            public void onInputAdded(String inputId) {
638                synchronized (mLock) {
639                    mStateMap.put(inputId, INPUT_STATE_CONNECTED);
640                    for (TvInputListenerRecord record : mTvInputListenerRecordsList) {
641                        record.postInputAdded(inputId);
642                    }
643                }
644            }
645
646            @Override
647            public void onInputRemoved(String inputId) {
648                synchronized (mLock) {
649                    mStateMap.remove(inputId);
650                    for (TvInputListenerRecord record : mTvInputListenerRecordsList) {
651                        record.postInputRemoved(inputId);
652                    }
653                }
654            }
655        };
656        try {
657            mService.registerCallback(mCallback, mUserId);
658        } catch (RemoteException e) {
659            Log.e(TAG, "mService.registerCallback failed: " + e);
660        }
661    }
662
663    /**
664     * Returns the complete list of TV inputs on the system.
665     *
666     * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
667     */
668    public List<TvInputInfo> getTvInputList() {
669        try {
670            return mService.getTvInputList(mUserId);
671        } catch (RemoteException e) {
672            throw new RuntimeException(e);
673        }
674    }
675
676    /**
677     * Returns the {@link TvInputInfo} for a given TV input.
678     *
679     * @param inputId The ID of the TV input.
680     * @return the {@link TvInputInfo} for a given TV input. {@code null} if not found.
681     */
682    public TvInputInfo getTvInputInfo(String inputId) {
683        if (inputId == null) {
684            throw new IllegalArgumentException("inputId cannot be null");
685        }
686        try {
687            return mService.getTvInputInfo(inputId, mUserId);
688        } catch (RemoteException e) {
689            throw new RuntimeException(e);
690        }
691    }
692
693    /**
694     * Returns the state of a given TV input. It retuns one of the following:
695     * <ul>
696     * <li>{@link #INPUT_STATE_CONNECTED}
697     * <li>{@link #INPUT_STATE_CONNECTED_STANDBY}
698     * <li>{@link #INPUT_STATE_DISCONNECTED}
699     * </ul>
700     *
701     * @param inputId The id of the TV input.
702     * @throws IllegalArgumentException if the argument is {@code null} or if there is no
703     *        {@link TvInputInfo} corresponding to {@code inputId}.
704     */
705    public int getInputState(String inputId) {
706        if (inputId == null) {
707            throw new IllegalArgumentException("inputId cannot be null");
708        }
709        synchronized (mLock) {
710            Integer state = mStateMap.get(inputId);
711            if (state == null) {
712                throw new IllegalArgumentException("Unrecognized input ID: " + inputId);
713            }
714            return state.intValue();
715        }
716    }
717
718    /**
719     * Registers a {@link TvInputListener}.
720     *
721     * @param listener A listener used to monitor status of the TV inputs.
722     * @param handler A {@link Handler} that the status change will be delivered to.
723     * @throws IllegalArgumentException if any of the arguments is {@code null}.
724     */
725    public void registerListener(TvInputListener listener, Handler handler) {
726        if (listener == null) {
727            throw new IllegalArgumentException("callback cannot be null");
728        }
729        if (handler == null) {
730            throw new IllegalArgumentException("handler cannot be null");
731        }
732        synchronized (mLock) {
733            mTvInputListenerRecordsList.add(new TvInputListenerRecord(listener, handler));
734        }
735    }
736
737    /**
738     * Unregisters the existing {@link TvInputListener}.
739     *
740     * @param listener The existing listener to remove.
741     * @throws IllegalArgumentException if any of the arguments is {@code null}.
742     */
743    public void unregisterListener(final TvInputListener listener) {
744        if (listener == null) {
745            throw new IllegalArgumentException("callback cannot be null");
746        }
747        synchronized (mLock) {
748            for (Iterator<TvInputListenerRecord> it = mTvInputListenerRecordsList.iterator();
749                    it.hasNext(); ) {
750                TvInputListenerRecord record = it.next();
751                if (record.getListener() == listener) {
752                    it.remove();
753                    break;
754                }
755            }
756        }
757    }
758
759    /**
760     * Returns the user's parental controls enabled state.
761     *
762     * @return {@code true} if the user enabled the parental controls, {@code false} otherwise.
763     */
764    public boolean isParentalControlsEnabled() {
765        try {
766            return mService.isParentalControlsEnabled(mUserId);
767        } catch (RemoteException e) {
768            throw new RuntimeException(e);
769        }
770    }
771
772    /**
773     * Sets the user's parental controls enabled state.
774     *
775     * @param enabled The user's parental controls enabled state. {@code true} if the user enabled
776     *            the parental controls, {@code false} otherwise.
777     * @see #isParentalControlsEnabled
778     * @hide
779     */
780    @SystemApi
781    public void setParentalControlsEnabled(boolean enabled) {
782        try {
783            mService.setParentalControlsEnabled(enabled, mUserId);
784        } catch (RemoteException e) {
785            throw new RuntimeException(e);
786        }
787    }
788
789    /**
790     * Checks whether a given TV content rating is blocked by the user.
791     *
792     * @param rating The TV content rating to check.
793     * @return {@code true} if the given TV content rating is blocked, {@code false} otherwise.
794     */
795    public boolean isRatingBlocked(TvContentRating rating) {
796        if (rating == null) {
797            throw new IllegalArgumentException("rating cannot be null");
798        }
799        try {
800            return mService.isRatingBlocked(rating.flattenToString(), mUserId);
801        } catch (RemoteException e) {
802            throw new RuntimeException(e);
803        }
804    }
805
806    /**
807     * Returns the list of blocked content ratings.
808     *
809     * @return the list of content ratings blocked by the user.
810     * @hide
811     */
812    @SystemApi
813    public List<TvContentRating> getBlockedRatings() {
814        try {
815            List<TvContentRating> ratings = new ArrayList<TvContentRating>();
816            for (String rating : mService.getBlockedRatings(mUserId)) {
817                ratings.add(TvContentRating.unflattenFromString(rating));
818            }
819            return ratings;
820        } catch (RemoteException e) {
821            throw new RuntimeException(e);
822        }
823    }
824
825    /**
826     * Adds a user blocked content rating.
827     *
828     * @param rating The content rating to block.
829     * @see #isRatingBlocked
830     * @see #removeBlockedRating
831     * @hide
832     */
833    @SystemApi
834    public void addBlockedRating(TvContentRating rating) {
835        if (rating == null) {
836            throw new IllegalArgumentException("rating cannot be null");
837        }
838        try {
839            mService.addBlockedRating(rating.flattenToString(), mUserId);
840        } catch (RemoteException e) {
841            throw new RuntimeException(e);
842        }
843    }
844
845    /**
846     * Removes a user blocked content rating.
847     *
848     * @param rating The content rating to unblock.
849     * @see #isRatingBlocked
850     * @see #addBlockedRating
851     * @hide
852     */
853    @SystemApi
854    public void removeBlockedRating(TvContentRating rating) {
855        if (rating == null) {
856            throw new IllegalArgumentException("rating cannot be null");
857        }
858        try {
859            mService.removeBlockedRating(rating.flattenToString(), mUserId);
860        } catch (RemoteException e) {
861            throw new RuntimeException(e);
862        }
863    }
864
865    /**
866     * Returns the list of xml resource uris for TV content rating systems.
867     * @hide
868     */
869    @SystemApi
870    public List<Uri> getTvContentRatingSystemXmls() {
871        try {
872            return mService.getTvContentRatingSystemXmls(mUserId);
873        } catch (RemoteException e) {
874            throw new RuntimeException(e);
875        }
876    }
877
878    /**
879     * Creates a {@link Session} for a given TV input.
880     * <p>
881     * The number of sessions that can be created at the same time is limited by the capability of
882     * the given TV input.
883     * </p>
884     *
885     * @param inputId The id of the TV input.
886     * @param callback A callback used to receive the created session.
887     * @param handler A {@link Handler} that the session creation will be delivered to.
888     * @throws IllegalArgumentException if any of the arguments is {@code null}.
889     * @hide
890     */
891    @SystemApi
892    public void createSession(String inputId, final SessionCallback callback,
893            Handler handler) {
894        if (inputId == null) {
895            throw new IllegalArgumentException("id cannot be null");
896        }
897        if (callback == null) {
898            throw new IllegalArgumentException("callback cannot be null");
899        }
900        if (handler == null) {
901            throw new IllegalArgumentException("handler cannot be null");
902        }
903        SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
904        synchronized (mSessionCallbackRecordMap) {
905            int seq = mNextSeq++;
906            mSessionCallbackRecordMap.put(seq, record);
907            try {
908                mService.createSession(mClient, inputId, seq, mUserId);
909            } catch (RemoteException e) {
910                throw new RuntimeException(e);
911            }
912        }
913    }
914
915    /**
916     * Returns the TvStreamConfig list of the given TV input.
917     *
918     * @param inputId the id of the TV input.
919     * @return List of {@link TvStreamConfig} which is available for capturing
920     *   of the given TV input.
921     * @hide
922     */
923    @SystemApi
924    public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId) {
925        try {
926            return mService.getAvailableTvStreamConfigList(inputId, mUserId);
927        } catch (RemoteException e) {
928            throw new RuntimeException(e);
929        }
930    }
931
932    /**
933     * Take a snapshot of the given TV input into the provided Surface.
934     *
935     * @param inputId the id of the TV input.
936     * @param surface the {@link Surface} to which the snapshot is captured.
937     * @param config the {@link TvStreamConfig} which is used for capturing.
938     * @return true when the {@link Surface} is ready to be captured.
939     * @hide
940     */
941    @SystemApi
942    public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config) {
943        try {
944            return mService.captureFrame(inputId, surface, config, mUserId);
945        } catch (RemoteException e) {
946            throw new RuntimeException(e);
947        }
948    }
949
950    /**
951     * The Session provides the per-session functionality of TV inputs.
952     * @hide
953     */
954    @SystemApi
955    public static final class Session {
956        static final int DISPATCH_IN_PROGRESS = -1;
957        static final int DISPATCH_NOT_HANDLED = 0;
958        static final int DISPATCH_HANDLED = 1;
959
960        private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
961
962        private final ITvInputManager mService;
963        private final int mUserId;
964        private final int mSeq;
965
966        // For scheduling input event handling on the main thread. This also serves as a lock to
967        // protect pending input events and the input channel.
968        private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
969
970        private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
971        private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
972        private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
973
974        private IBinder mToken;
975        private TvInputEventSender mSender;
976        private InputChannel mChannel;
977        private final List<TvTrackInfo> mAudioTracks = new ArrayList<TvTrackInfo>();
978        private final List<TvTrackInfo> mVideoTracks = new ArrayList<TvTrackInfo>();
979        private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<TvTrackInfo>();
980        private String mSelectedAudioTrackId;
981        private String mSelectedVideoTrackId;
982        private String mSelectedSubtitleTrackId;
983
984        private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
985                int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
986            mToken = token;
987            mChannel = channel;
988            mService = service;
989            mUserId = userId;
990            mSeq = seq;
991            mSessionCallbackRecordMap = sessionCallbackRecordMap;
992        }
993
994        /**
995         * Releases this session.
996         */
997        public void release() {
998            if (mToken == null) {
999                Log.w(TAG, "The session has been already released");
1000                return;
1001            }
1002            try {
1003                mService.releaseSession(mToken, mUserId);
1004            } catch (RemoteException e) {
1005                throw new RuntimeException(e);
1006            }
1007
1008            releaseInternal();
1009        }
1010
1011        /**
1012         * Sets this as main session. See {@link TvView#setMainTvView} for about meaning of "main".
1013         * @hide
1014         */
1015        public void setMainSession() {
1016            if (mToken == null) {
1017                Log.w(TAG, "The session has been already released");
1018                return;
1019            }
1020            try {
1021                mService.setMainSession(mToken, mUserId);
1022            } catch (RemoteException e) {
1023                throw new RuntimeException(e);
1024            }
1025        }
1026
1027        /**
1028         * Sets the {@link android.view.Surface} for this session.
1029         *
1030         * @param surface A {@link android.view.Surface} used to render video.
1031         */
1032        public void setSurface(Surface surface) {
1033            if (mToken == null) {
1034                Log.w(TAG, "The session has been already released");
1035                return;
1036            }
1037            // surface can be null.
1038            try {
1039                mService.setSurface(mToken, surface, mUserId);
1040            } catch (RemoteException e) {
1041                throw new RuntimeException(e);
1042            }
1043        }
1044
1045        /**
1046         * Notifies of any structural changes (format or size) of the {@link Surface}
1047         * passed by {@link #setSurface}.
1048         *
1049         * @param format The new PixelFormat of the {@link Surface}.
1050         * @param width The new width of the {@link Surface}.
1051         * @param height The new height of the {@link Surface}.
1052         * @hide
1053         */
1054        @SystemApi
1055        public void dispatchSurfaceChanged(int format, int width, int height) {
1056            if (mToken == null) {
1057                Log.w(TAG, "The session has been already released");
1058                return;
1059            }
1060            try {
1061                mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
1062            } catch (RemoteException e) {
1063                throw new RuntimeException(e);
1064            }
1065        }
1066
1067        /**
1068         * Sets the relative stream volume of this session to handle a change of audio focus.
1069         *
1070         * @param volume A volume value between 0.0f to 1.0f.
1071         * @throws IllegalArgumentException if the volume value is out of range.
1072         */
1073        public void setStreamVolume(float volume) {
1074            if (mToken == null) {
1075                Log.w(TAG, "The session has been already released");
1076                return;
1077            }
1078            try {
1079                if (volume < 0.0f || volume > 1.0f) {
1080                    throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
1081                }
1082                mService.setVolume(mToken, volume, mUserId);
1083            } catch (RemoteException e) {
1084                throw new RuntimeException(e);
1085            }
1086        }
1087
1088        /**
1089         * Tunes to a given channel.
1090         *
1091         * @param channelUri The URI of a channel.
1092         * @throws IllegalArgumentException if the argument is {@code null}.
1093         */
1094        public void tune(Uri channelUri) {
1095            tune(channelUri, null);
1096        }
1097
1098        /**
1099         * Tunes to a given channel.
1100         *
1101         * @param channelUri The URI of a channel.
1102         * @param params A set of extra parameters which might be handled with this tune event.
1103         * @throws IllegalArgumentException if {@code channelUri} is {@code null}.
1104         * @hide
1105         */
1106        @SystemApi
1107        public void tune(Uri channelUri, Bundle params) {
1108            if (channelUri == null) {
1109                throw new IllegalArgumentException("channelUri cannot be null");
1110            }
1111            if (mToken == null) {
1112                Log.w(TAG, "The session has been already released");
1113                return;
1114            }
1115            mAudioTracks.clear();
1116            mVideoTracks.clear();
1117            mSubtitleTracks.clear();
1118            mSelectedAudioTrackId = null;
1119            mSelectedVideoTrackId = null;
1120            mSelectedSubtitleTrackId = null;
1121            try {
1122                mService.tune(mToken, channelUri, params, mUserId);
1123            } catch (RemoteException e) {
1124                throw new RuntimeException(e);
1125            }
1126        }
1127
1128        /**
1129         * Enables or disables the caption for this session.
1130         *
1131         * @param enabled {@code true} to enable, {@code false} to disable.
1132         */
1133        public void setCaptionEnabled(boolean enabled) {
1134            if (mToken == null) {
1135                Log.w(TAG, "The session has been already released");
1136                return;
1137            }
1138            try {
1139                mService.setCaptionEnabled(mToken, enabled, mUserId);
1140            } catch (RemoteException e) {
1141                throw new RuntimeException(e);
1142            }
1143        }
1144
1145        /**
1146         * Selects a track.
1147         *
1148         * @param type The type of the track to select. The type can be
1149         *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
1150         *            {@link TvTrackInfo#TYPE_SUBTITLE}.
1151         * @param trackId The ID of the track to select. When {@code null}, the currently selected
1152         *            track of the given type will be unselected.
1153         * @see #getTracks()
1154         */
1155        public void selectTrack(int type, String trackId) {
1156            if (type == TvTrackInfo.TYPE_AUDIO) {
1157                if (trackId != null && !mAudioTracks.contains(trackId)) {
1158                    Log.w(TAG, "Invalid audio trackId: " + trackId);
1159                }
1160            } else if (type == TvTrackInfo.TYPE_VIDEO) {
1161                if (trackId != null && !mVideoTracks.contains(trackId)) {
1162                    Log.w(TAG, "Invalid video trackId: " + trackId);
1163                }
1164            } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1165                if (trackId != null && !mSubtitleTracks.contains(trackId)) {
1166                    Log.w(TAG, "Invalid subtitle trackId: " + trackId);
1167                }
1168            } else {
1169                throw new IllegalArgumentException("invalid type: " + type);
1170            }
1171            if (mToken == null) {
1172                Log.w(TAG, "The session has been already released");
1173                return;
1174            }
1175            try {
1176                mService.selectTrack(mToken, type, trackId, mUserId);
1177            } catch (RemoteException e) {
1178                throw new RuntimeException(e);
1179            }
1180        }
1181
1182        /**
1183         * Returns the list of tracks for a given type. Returns {@code null} if the information is
1184         * not available.
1185         *
1186         * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
1187         *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
1188         * @return the list of tracks for the given type.
1189         */
1190        public List<TvTrackInfo> getTracks(int type) {
1191            if (type == TvTrackInfo.TYPE_AUDIO) {
1192                if (mAudioTracks == null) {
1193                    return null;
1194                }
1195                return mAudioTracks;
1196            } else if (type == TvTrackInfo.TYPE_VIDEO) {
1197                if (mVideoTracks == null) {
1198                    return null;
1199                }
1200                return mVideoTracks;
1201            } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1202                if (mSubtitleTracks == null) {
1203                    return null;
1204                }
1205                return mSubtitleTracks;
1206            }
1207            throw new IllegalArgumentException("invalid type: " + type);
1208        }
1209
1210        /**
1211         * Returns the selected track for a given type. Returns {@code null} if the information is
1212         * not available or any of the tracks for the given type is not selected.
1213         *
1214         * @return the ID of the selected track.
1215         * @see #selectTrack
1216         */
1217        public String getSelectedTrack(int type) {
1218            if (type == TvTrackInfo.TYPE_AUDIO) {
1219                return mSelectedAudioTrackId;
1220            } else if (type == TvTrackInfo.TYPE_VIDEO) {
1221                return mSelectedVideoTrackId;
1222            } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1223                return mSelectedSubtitleTrackId;
1224            }
1225            throw new IllegalArgumentException("invalid type: " + type);
1226        }
1227
1228        /**
1229         * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle)
1230         * TvInputService.Session.appPrivateCommand()} on the current TvView.
1231         *
1232         * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
1233         *            i.e. prefixed with a package name you own, so that different developers will
1234         *            not create conflicting commands.
1235         * @param data Any data to include with the command.
1236         * @hide
1237         */
1238        @SystemApi
1239        public void sendAppPrivateCommand(String action, Bundle data) {
1240            if (mToken == null) {
1241                Log.w(TAG, "The session has been already released");
1242                return;
1243            }
1244            try {
1245                mService.sendAppPrivateCommand(mToken, action, data, mUserId);
1246            } catch (RemoteException e) {
1247                throw new RuntimeException(e);
1248            }
1249        }
1250
1251        /**
1252         * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
1253         * should be called whenever the layout of its containing view is changed.
1254         * {@link #removeOverlayView()} should be called to remove the overlay view.
1255         * Since a session can have only one overlay view, this method should be called only once
1256         * or it can be called again after calling {@link #removeOverlayView()}.
1257         *
1258         * @param view A view playing TV.
1259         * @param frame A position of the overlay view.
1260         * @throws IllegalArgumentException if any of the arguments is {@code null}.
1261         * @throws IllegalStateException if {@code view} is not attached to a window.
1262         */
1263        void createOverlayView(View view, Rect frame) {
1264            if (view == null) {
1265                throw new IllegalArgumentException("view cannot be null");
1266            }
1267            if (frame == null) {
1268                throw new IllegalArgumentException("frame cannot be null");
1269            }
1270            if (view.getWindowToken() == null) {
1271                throw new IllegalStateException("view must be attached to a window");
1272            }
1273            if (mToken == null) {
1274                Log.w(TAG, "The session has been already released");
1275                return;
1276            }
1277            try {
1278                mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
1279            } catch (RemoteException e) {
1280                throw new RuntimeException(e);
1281            }
1282        }
1283
1284        /**
1285         * Relayouts the current overlay view.
1286         *
1287         * @param frame A new position of the overlay view.
1288         * @throws IllegalArgumentException if the arguments is {@code null}.
1289         */
1290        void relayoutOverlayView(Rect frame) {
1291            if (frame == null) {
1292                throw new IllegalArgumentException("frame cannot be null");
1293            }
1294            if (mToken == null) {
1295                Log.w(TAG, "The session has been already released");
1296                return;
1297            }
1298            try {
1299                mService.relayoutOverlayView(mToken, frame, mUserId);
1300            } catch (RemoteException e) {
1301                throw new RuntimeException(e);
1302            }
1303        }
1304
1305        /**
1306         * Removes the current overlay view.
1307         */
1308        void removeOverlayView() {
1309            if (mToken == null) {
1310                Log.w(TAG, "The session has been already released");
1311                return;
1312            }
1313            try {
1314                mService.removeOverlayView(mToken, mUserId);
1315            } catch (RemoteException e) {
1316                throw new RuntimeException(e);
1317            }
1318        }
1319
1320        /**
1321         * Requests to unblock content blocked by parental controls.
1322         */
1323        void requestUnblockContent(TvContentRating unblockedRating) {
1324            if (mToken == null) {
1325                Log.w(TAG, "The session has been already released");
1326                return;
1327            }
1328            try {
1329                mService.requestUnblockContent(mToken, unblockedRating.flattenToString(), mUserId);
1330            } catch (RemoteException e) {
1331                throw new RuntimeException(e);
1332            }
1333        }
1334
1335        /**
1336         * Dispatches an input event to this session.
1337         *
1338         * @param event An {@link InputEvent} to dispatch.
1339         * @param token A token used to identify the input event later in the callback.
1340         * @param callback A callback used to receive the dispatch result.
1341         * @param handler A {@link Handler} that the dispatch result will be delivered to.
1342         * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
1343         *         {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
1344         *         {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
1345         *         be invoked later.
1346         * @throws IllegalArgumentException if any of the necessary arguments is {@code null}.
1347         * @hide
1348         */
1349        public int dispatchInputEvent(InputEvent event, Object token,
1350                FinishedInputEventCallback callback, Handler handler) {
1351            if (event == null) {
1352                throw new IllegalArgumentException("event cannot be null");
1353            }
1354            if (callback != null && handler == null) {
1355                throw new IllegalArgumentException("handler cannot be null");
1356            }
1357            synchronized (mHandler) {
1358                if (mChannel == null) {
1359                    return DISPATCH_NOT_HANDLED;
1360                }
1361                PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
1362                if (Looper.myLooper() == Looper.getMainLooper()) {
1363                    // Already running on the main thread so we can send the event immediately.
1364                    return sendInputEventOnMainLooperLocked(p);
1365                }
1366
1367                // Post the event to the main thread.
1368                Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
1369                msg.setAsynchronous(true);
1370                mHandler.sendMessage(msg);
1371                return DISPATCH_IN_PROGRESS;
1372            }
1373        }
1374
1375        /**
1376         * Callback that is invoked when an input event that was dispatched to this session has been
1377         * finished.
1378         *
1379         * @hide
1380         */
1381        public interface FinishedInputEventCallback {
1382            /**
1383             * Called when the dispatched input event is finished.
1384             *
1385             * @param token A token passed to {@link #dispatchInputEvent}.
1386             * @param handled {@code true} if the dispatched input event was handled properly.
1387             *            {@code false} otherwise.
1388             */
1389            public void onFinishedInputEvent(Object token, boolean handled);
1390        }
1391
1392        // Must be called on the main looper
1393        private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
1394            synchronized (mHandler) {
1395                int result = sendInputEventOnMainLooperLocked(p);
1396                if (result == DISPATCH_IN_PROGRESS) {
1397                    return;
1398                }
1399            }
1400
1401            invokeFinishedInputEventCallback(p, false);
1402        }
1403
1404        private int sendInputEventOnMainLooperLocked(PendingEvent p) {
1405            if (mChannel != null) {
1406                if (mSender == null) {
1407                    mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
1408                }
1409
1410                final InputEvent event = p.mEvent;
1411                final int seq = event.getSequenceNumber();
1412                if (mSender.sendInputEvent(seq, event)) {
1413                    mPendingEvents.put(seq, p);
1414                    Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
1415                    msg.setAsynchronous(true);
1416                    mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
1417                    return DISPATCH_IN_PROGRESS;
1418                }
1419
1420                Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
1421                        + event);
1422            }
1423            return DISPATCH_NOT_HANDLED;
1424        }
1425
1426        void finishedInputEvent(int seq, boolean handled, boolean timeout) {
1427            final PendingEvent p;
1428            synchronized (mHandler) {
1429                int index = mPendingEvents.indexOfKey(seq);
1430                if (index < 0) {
1431                    return; // spurious, event already finished or timed out
1432                }
1433
1434                p = mPendingEvents.valueAt(index);
1435                mPendingEvents.removeAt(index);
1436
1437                if (timeout) {
1438                    Log.w(TAG, "Timeout waiting for seesion to handle input event after "
1439                            + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
1440                } else {
1441                    mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
1442                }
1443            }
1444
1445            invokeFinishedInputEventCallback(p, handled);
1446        }
1447
1448        // Assumes the event has already been removed from the queue.
1449        void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
1450            p.mHandled = handled;
1451            if (p.mHandler.getLooper().isCurrentThread()) {
1452                // Already running on the callback handler thread so we can send the callback
1453                // immediately.
1454                p.run();
1455            } else {
1456                // Post the event to the callback handler thread.
1457                // In this case, the callback will be responsible for recycling the event.
1458                Message msg = Message.obtain(p.mHandler, p);
1459                msg.setAsynchronous(true);
1460                msg.sendToTarget();
1461            }
1462        }
1463
1464        private void flushPendingEventsLocked() {
1465            mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
1466
1467            final int count = mPendingEvents.size();
1468            for (int i = 0; i < count; i++) {
1469                int seq = mPendingEvents.keyAt(i);
1470                Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
1471                msg.setAsynchronous(true);
1472                msg.sendToTarget();
1473            }
1474        }
1475
1476        private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
1477                FinishedInputEventCallback callback, Handler handler) {
1478            PendingEvent p = mPendingEventPool.acquire();
1479            if (p == null) {
1480                p = new PendingEvent();
1481            }
1482            p.mEvent = event;
1483            p.mToken = token;
1484            p.mCallback = callback;
1485            p.mHandler = handler;
1486            return p;
1487        }
1488
1489        private void recyclePendingEventLocked(PendingEvent p) {
1490            p.recycle();
1491            mPendingEventPool.release(p);
1492        }
1493
1494        IBinder getToken() {
1495            return mToken;
1496        }
1497
1498        private void releaseInternal() {
1499            mToken = null;
1500            synchronized (mHandler) {
1501                if (mChannel != null) {
1502                    if (mSender != null) {
1503                        flushPendingEventsLocked();
1504                        mSender.dispose();
1505                        mSender = null;
1506                    }
1507                    mChannel.dispose();
1508                    mChannel = null;
1509                }
1510            }
1511            synchronized (mSessionCallbackRecordMap) {
1512                mSessionCallbackRecordMap.remove(mSeq);
1513            }
1514        }
1515
1516        private final class InputEventHandler extends Handler {
1517            public static final int MSG_SEND_INPUT_EVENT = 1;
1518            public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
1519            public static final int MSG_FLUSH_INPUT_EVENT = 3;
1520
1521            InputEventHandler(Looper looper) {
1522                super(looper, null, true);
1523            }
1524
1525            @Override
1526            public void handleMessage(Message msg) {
1527                switch (msg.what) {
1528                    case MSG_SEND_INPUT_EVENT: {
1529                        sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
1530                        return;
1531                    }
1532                    case MSG_TIMEOUT_INPUT_EVENT: {
1533                        finishedInputEvent(msg.arg1, false, true);
1534                        return;
1535                    }
1536                    case MSG_FLUSH_INPUT_EVENT: {
1537                        finishedInputEvent(msg.arg1, false, false);
1538                        return;
1539                    }
1540                }
1541            }
1542        }
1543
1544        private final class TvInputEventSender extends InputEventSender {
1545            public TvInputEventSender(InputChannel inputChannel, Looper looper) {
1546                super(inputChannel, looper);
1547            }
1548
1549            @Override
1550            public void onInputEventFinished(int seq, boolean handled) {
1551                finishedInputEvent(seq, handled, false);
1552            }
1553        }
1554
1555        private final class PendingEvent implements Runnable {
1556            public InputEvent mEvent;
1557            public Object mToken;
1558            public FinishedInputEventCallback mCallback;
1559            public Handler mHandler;
1560            public boolean mHandled;
1561
1562            public void recycle() {
1563                mEvent = null;
1564                mToken = null;
1565                mCallback = null;
1566                mHandler = null;
1567                mHandled = false;
1568            }
1569
1570            @Override
1571            public void run() {
1572                mCallback.onFinishedInputEvent(mToken, mHandled);
1573
1574                synchronized (mHandler) {
1575                    recyclePendingEventLocked(this);
1576                }
1577            }
1578        }
1579    }
1580}
1581