2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.media.tv;
19 import android.graphics.Rect;
20 import android.net.Uri;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.os.IBinder;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.os.RemoteException;
27 import android.util.Log;
28 import android.util.Pools.Pool;
29 import android.util.Pools.SimplePool;
30 import android.util.SparseArray;
31 import android.view.InputChannel;
32 import android.view.InputEvent;
33 import android.view.InputEventSender;
34 import android.view.Surface;
35 import android.view.View;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
44 * Central system API to the overall TV input framework (TIF) architecture, which arbitrates
45 * interaction between applications and the selected TV inputs.
47 public final class TvInputManager {
48 private static final String TAG = "TvInputManager";
50 private final ITvInputManager mService;
52 // A mapping from an input to the list of its TvInputListenerRecords.
53 private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
54 new HashMap<String, List<TvInputListenerRecord>>();
56 // A mapping from the sequence number of a session to its SessionCallbackRecord.
57 private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
58 new SparseArray<SessionCallbackRecord>();
60 // A sequence number for the next session to be created. Should be protected by a lock
61 // {@code mSessionCallbackRecordMap}.
64 private final ITvInputClient mClient;
66 private final int mUserId;
69 * Interface used to receive the created session.
72 public abstract static class SessionCallback {
74 * This is called after {@link TvInputManager#createSession} has been processed.
76 * @param session A {@link TvInputManager.Session} instance created. This can be
77 * {@code null} if the creation request failed.
79 public void onSessionCreated(Session session) {
83 * This is called when {@link TvInputManager.Session} is released.
84 * This typically happens when the process hosting the session has crashed or been killed.
86 * @param session A {@link TvInputManager.Session} instance released.
88 public void onSessionReleased(Session session) {
92 * This is called when the channel of this session is changed by the underlying TV input
93 * with out any {@link TvInputManager.Session#tune(Uri)} request.
95 * @param session A {@link TvInputManager.Session} associated with this callback
96 * @param channelUri The URI of a channel.
98 public void onChannelRetuned(Session session, Uri channelUri) {
102 * This is called when the track information of the session has been changed.
104 * @param session A {@link TvInputManager.Session} associated with this callback
105 * @param tracks A list which includes track information.
107 public void onTrackInfoChanged(Session session, List<TvTrackInfo> tracks) {
111 * This is called when a custom event has been sent from this session.
113 * @param session A {@link TvInputManager.Session} associated with this callback
114 * @param eventType The type of the event.
115 * @param eventArgs Optional arguments of the event.
118 public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
122 private static final class SessionCallbackRecord {
123 private final SessionCallback mSessionCallback;
124 private final Handler mHandler;
125 private Session mSession;
127 public SessionCallbackRecord(SessionCallback sessionCallback,
129 mSessionCallback = sessionCallback;
133 public void postSessionCreated(final Session session) {
135 mHandler.post(new Runnable() {
138 mSessionCallback.onSessionCreated(session);
143 public void postSessionReleased() {
144 mHandler.post(new Runnable() {
147 mSessionCallback.onSessionReleased(mSession);
152 public void postChannelRetuned(final Uri channelUri) {
153 mHandler.post(new Runnable() {
156 mSessionCallback.onChannelRetuned(mSession, channelUri);
161 public void postTrackInfoChanged(final List<TvTrackInfo> tracks) {
162 mHandler.post(new Runnable() {
165 mSession.setTracks(tracks);
166 mSessionCallback.onTrackInfoChanged(mSession, tracks);
171 public void postSessionEvent(final String eventType, final Bundle eventArgs) {
172 mHandler.post(new Runnable() {
175 mSessionCallback.onSessionEvent(mSession, eventType, eventArgs);
182 * Interface used to monitor status of the TV input.
184 public abstract static class TvInputListener {
186 * This is called when the availability status of a given TV input is changed.
188 * @param inputId the id of the TV input.
189 * @param isAvailable {@code true} if the given TV input is available to show TV programs.
190 * {@code false} otherwise.
192 public void onAvailabilityChanged(String inputId, boolean isAvailable) {
196 private static final class TvInputListenerRecord {
197 private final TvInputListener mListener;
198 private final Handler mHandler;
200 public TvInputListenerRecord(TvInputListener listener, Handler handler) {
201 mListener = listener;
205 public TvInputListener getListener() {
209 public void postAvailabilityChanged(final String inputId, final boolean isAvailable) {
210 mHandler.post(new Runnable() {
213 mListener.onAvailabilityChanged(inputId, isAvailable);
222 public TvInputManager(ITvInputManager service, int userId) {
225 mClient = new ITvInputClient.Stub() {
227 public void onSessionCreated(String inputId, IBinder token, InputChannel channel,
229 synchronized (mSessionCallbackRecordMap) {
230 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
231 if (record == null) {
232 Log.e(TAG, "Callback not found for " + token);
235 Session session = null;
237 session = new Session(token, channel, mService, mUserId, seq,
238 mSessionCallbackRecordMap);
240 record.postSessionCreated(session);
245 public void onSessionReleased(int seq) {
246 synchronized (mSessionCallbackRecordMap) {
247 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
248 mSessionCallbackRecordMap.delete(seq);
249 if (record == null) {
250 Log.e(TAG, "Callback not found for seq:" + seq);
253 record.mSession.releaseInternal();
254 record.postSessionReleased();
259 public void onChannelRetuned(Uri channelUri, int seq) {
260 synchronized (mSessionCallbackRecordMap) {
261 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
262 if (record == null) {
263 Log.e(TAG, "Callback not found for seq " + seq);
266 record.postChannelRetuned(channelUri);
271 public void onTrackInfoChanged(List<TvTrackInfo> tracks, int seq) {
272 synchronized (mSessionCallbackRecordMap) {
273 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
274 if (record == null) {
275 Log.e(TAG, "Callback not found for seq " + seq);
278 record.postTrackInfoChanged(tracks);
283 public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
284 synchronized (mSessionCallbackRecordMap) {
285 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
286 if (record == null) {
287 Log.e(TAG, "Callback not found for seq " + seq);
290 record.postSessionEvent(eventType, eventArgs);
295 public void onAvailabilityChanged(String inputId, boolean isAvailable) {
296 synchronized (mTvInputListenerRecordsMap) {
297 List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
298 if (records == null) {
299 // Silently ignore - no listener is registered yet.
302 int recordsCount = records.size();
303 for (int i = 0; i < recordsCount; i++) {
304 records.get(i).postAvailabilityChanged(inputId, isAvailable);
312 * Returns the complete list of TV inputs on the system.
314 * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
316 public List<TvInputInfo> getTvInputList() {
318 return mService.getTvInputList(mUserId);
319 } catch (RemoteException e) {
320 throw new RuntimeException(e);
325 * Returns the availability of a given TV input.
327 * @param inputId the id of the TV input.
328 * @throws IllegalArgumentException if the argument is {@code null}.
329 * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
332 public boolean getAvailability(String inputId) {
333 if (inputId == null) {
334 throw new IllegalArgumentException("id cannot be null");
336 synchronized (mTvInputListenerRecordsMap) {
337 List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
338 if (records == null || records.size() == 0) {
339 throw new IllegalStateException("At least one listener should be registered.");
343 return mService.getAvailability(mClient, inputId, mUserId);
344 } catch (RemoteException e) {
345 throw new RuntimeException(e);
350 * Registers a {@link TvInputListener} for a given TV input.
352 * @param inputId the id of the TV input.
353 * @param listener a listener used to monitor status of the given TV input.
354 * @param handler a {@link Handler} that the status change will be delivered to.
355 * @throws IllegalArgumentException if any of the arguments is {@code null}.
358 public void registerListener(String inputId, TvInputListener listener, Handler handler) {
359 if (inputId == null) {
360 throw new IllegalArgumentException("id cannot be null");
362 if (listener == null) {
363 throw new IllegalArgumentException("listener cannot be null");
365 if (handler == null) {
366 throw new IllegalArgumentException("handler cannot be null");
368 synchronized (mTvInputListenerRecordsMap) {
369 List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
370 if (records == null) {
371 records = new ArrayList<TvInputListenerRecord>();
372 mTvInputListenerRecordsMap.put(inputId, records);
374 mService.registerCallback(mClient, inputId, mUserId);
375 } catch (RemoteException e) {
376 throw new RuntimeException(e);
379 records.add(new TvInputListenerRecord(listener, handler));
384 * Unregisters the existing {@link TvInputListener} for a given TV input.
386 * @param inputId the id of the TV input.
387 * @param listener the existing listener to remove for the given TV input.
388 * @throws IllegalArgumentException if any of the arguments is {@code null}.
391 public void unregisterListener(String inputId, final TvInputListener listener) {
392 if (inputId == null) {
393 throw new IllegalArgumentException("id cannot be null");
395 if (listener == null) {
396 throw new IllegalArgumentException("listener cannot be null");
398 synchronized (mTvInputListenerRecordsMap) {
399 List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
400 if (records == null) {
401 Log.e(TAG, "No listener found for " + inputId);
404 for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
405 TvInputListenerRecord record = it.next();
406 if (record.getListener() == listener) {
410 if (records.isEmpty()) {
412 mService.unregisterCallback(mClient, inputId, mUserId);
413 } catch (RemoteException e) {
414 throw new RuntimeException(e);
416 mTvInputListenerRecordsMap.remove(inputId);
423 * Creates a {@link Session} for a given TV input.
425 * The number of sessions that can be created at the same time is limited by the capability of
426 * the given TV input.
429 * @param inputId the id of the TV input.
430 * @param callback a callback used to receive the created session.
431 * @param handler a {@link Handler} that the session creation will be delivered to.
432 * @throws IllegalArgumentException if any of the arguments is {@code null}.
435 public void createSession(String inputId, final SessionCallback callback,
437 if (inputId == null) {
438 throw new IllegalArgumentException("id cannot be null");
440 if (callback == null) {
441 throw new IllegalArgumentException("callback cannot be null");
443 if (handler == null) {
444 throw new IllegalArgumentException("handler cannot be null");
446 SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
447 synchronized (mSessionCallbackRecordMap) {
448 int seq = mNextSeq++;
449 mSessionCallbackRecordMap.put(seq, record);
451 mService.createSession(mClient, inputId, seq, mUserId);
452 } catch (RemoteException e) {
453 throw new RuntimeException(e);
459 * The Session provides the per-session functionality of TV inputs.
462 public static final class Session {
463 static final int DISPATCH_IN_PROGRESS = -1;
464 static final int DISPATCH_NOT_HANDLED = 0;
465 static final int DISPATCH_HANDLED = 1;
467 private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
469 private final ITvInputManager mService;
470 private final int mUserId;
471 private final int mSeq;
473 // For scheduling input event handling on the main thread. This also serves as a lock to
474 // protect pending input events and the input channel.
475 private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
477 private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
478 private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
479 private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
481 private IBinder mToken;
482 private TvInputEventSender mSender;
483 private InputChannel mChannel;
484 private List<TvTrackInfo> mTracks;
487 private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
488 int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
494 mSessionCallbackRecordMap = sessionCallbackRecordMap;
498 * Releases this session.
500 public void release() {
501 if (mToken == null) {
502 Log.w(TAG, "The session has been already released");
506 mService.releaseSession(mToken, mUserId);
507 } catch (RemoteException e) {
508 throw new RuntimeException(e);
515 * Sets the {@link android.view.Surface} for this session.
517 * @param surface A {@link android.view.Surface} used to render video.
520 public void setSurface(Surface surface) {
521 if (mToken == null) {
522 Log.w(TAG, "The session has been already released");
525 // surface can be null.
527 mService.setSurface(mToken, surface, mUserId);
528 } catch (RemoteException e) {
529 throw new RuntimeException(e);
534 * Sets the relative stream volume of this session to handle a change of audio focus.
536 * @param volume A volume value between 0.0f to 1.0f.
537 * @throws IllegalArgumentException if the volume value is out of range.
539 public void setStreamVolume(float volume) {
540 if (mToken == null) {
541 Log.w(TAG, "The session has been already released");
545 if (volume < 0.0f || volume > 1.0f) {
546 throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
548 mService.setVolume(mToken, volume, mUserId);
549 } catch (RemoteException e) {
550 throw new RuntimeException(e);
555 * Tunes to a given channel.
557 * @param channelUri The URI of a channel.
558 * @throws IllegalArgumentException if the argument is {@code null}.
560 public void tune(Uri channelUri) {
561 if (channelUri == null) {
562 throw new IllegalArgumentException("channelUri cannot be null");
564 if (mToken == null) {
565 Log.w(TAG, "The session has been already released");
570 mService.tune(mToken, channelUri, mUserId);
571 } catch (RemoteException e) {
572 throw new RuntimeException(e);
577 * Enables or disables the caption for this session.
579 * @param enabled {@code true} to enable, {@code false} to disable.
581 public void setCaptionEnabled(boolean enabled) {
582 if (mToken == null) {
583 Log.w(TAG, "The session has been already released");
587 mService.setCaptionEnabled(mToken, enabled, mUserId);
588 } catch (RemoteException e) {
589 throw new RuntimeException(e);
596 * @param track the track to be selected.
599 public void selectTrack(TvTrackInfo track) {
601 throw new IllegalArgumentException("track cannot be null");
603 if (mToken == null) {
604 Log.w(TAG, "The session has been already released");
608 mService.selectTrack(mToken, track, mUserId);
609 } catch (RemoteException e) {
610 throw new RuntimeException(e);
617 * @param track the track to be selected.
620 public void unselectTrack(TvTrackInfo track) {
622 throw new IllegalArgumentException("track cannot be null");
624 if (mToken == null) {
625 Log.w(TAG, "The session has been already released");
629 mService.unselectTrack(mToken, track, mUserId);
630 } catch (RemoteException e) {
631 throw new RuntimeException(e);
636 * Returns a list which includes track information. May return {@code null} if the
637 * information is not available.
638 * @see #selectTrack(TvTrackInfo)
639 * @see #unselectTrack(TvTrackInfo)
641 public List<TvTrackInfo> getTracks() {
642 if (mTracks == null) {
645 return new ArrayList<TvTrackInfo>(mTracks);
648 private void setTracks(List<TvTrackInfo> tracks) {
653 * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
654 * should be called whenever the layout of its containing view is changed.
655 * {@link #removeOverlayView()} should be called to remove the overlay view.
656 * Since a session can have only one overlay view, this method should be called only once
657 * or it can be called again after calling {@link #removeOverlayView()}.
659 * @param view A view playing TV.
660 * @param frame A position of the overlay view.
661 * @throws IllegalArgumentException if any of the arguments is {@code null}.
662 * @throws IllegalStateException if {@code view} is not attached to a window.
664 void createOverlayView(View view, Rect frame) {
666 throw new IllegalArgumentException("view cannot be null");
669 throw new IllegalArgumentException("frame cannot be null");
671 if (view.getWindowToken() == null) {
672 throw new IllegalStateException("view must be attached to a window");
674 if (mToken == null) {
675 Log.w(TAG, "The session has been already released");
679 mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
680 } catch (RemoteException e) {
681 throw new RuntimeException(e);
686 * Relayouts the current overlay view.
688 * @param frame A new position of the overlay view.
689 * @throws IllegalArgumentException if the arguments is {@code null}.
691 void relayoutOverlayView(Rect frame) {
693 throw new IllegalArgumentException("frame cannot be null");
695 if (mToken == null) {
696 Log.w(TAG, "The session has been already released");
700 mService.relayoutOverlayView(mToken, frame, mUserId);
701 } catch (RemoteException e) {
702 throw new RuntimeException(e);
707 * Removes the current overlay view.
709 void removeOverlayView() {
710 if (mToken == null) {
711 Log.w(TAG, "The session has been already released");
715 mService.removeOverlayView(mToken, mUserId);
716 } catch (RemoteException e) {
717 throw new RuntimeException(e);
722 * Dispatches an input event to this session.
724 * @param event {@link InputEvent} to dispatch.
725 * @param token A token used to identify the input event later in the callback.
726 * @param callback A callback used to receive the dispatch result.
727 * @param handler {@link Handler} that the dispatch result will be delivered to.
728 * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
729 * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
730 * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
732 * @throws IllegalArgumentException if any of the necessary arguments is {@code null}.
735 public int dispatchInputEvent(InputEvent event, Object token,
736 FinishedInputEventCallback callback, Handler handler) {
738 throw new IllegalArgumentException("event cannot be null");
740 if (callback != null && handler == null) {
741 throw new IllegalArgumentException("handler cannot be null");
743 synchronized (mHandler) {
744 if (mChannel == null) {
745 return DISPATCH_NOT_HANDLED;
747 PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
748 if (Looper.myLooper() == Looper.getMainLooper()) {
749 // Already running on the main thread so we can send the event immediately.
750 return sendInputEventOnMainLooperLocked(p);
753 // Post the event to the main thread.
754 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
755 msg.setAsynchronous(true);
756 mHandler.sendMessage(msg);
757 return DISPATCH_IN_PROGRESS;
762 * Callback that is invoked when an input event that was dispatched to this session has been
767 public interface FinishedInputEventCallback {
769 * Called when the dispatched input event is finished.
771 * @param token a token passed to {@link #dispatchInputEvent}.
772 * @param handled {@code true} if the dispatched input event was handled properly.
773 * {@code false} otherwise.
775 public void onFinishedInputEvent(Object token, boolean handled);
778 // Must be called on the main looper
779 private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
780 synchronized (mHandler) {
781 int result = sendInputEventOnMainLooperLocked(p);
782 if (result == DISPATCH_IN_PROGRESS) {
787 invokeFinishedInputEventCallback(p, false);
790 private int sendInputEventOnMainLooperLocked(PendingEvent p) {
791 if (mChannel != null) {
792 if (mSender == null) {
793 mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
796 final InputEvent event = p.mEvent;
797 final int seq = event.getSequenceNumber();
798 if (mSender.sendInputEvent(seq, event)) {
799 mPendingEvents.put(seq, p);
800 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
801 msg.setAsynchronous(true);
802 mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
803 return DISPATCH_IN_PROGRESS;
806 Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
809 return DISPATCH_NOT_HANDLED;
812 void finishedInputEvent(int seq, boolean handled, boolean timeout) {
813 final PendingEvent p;
814 synchronized (mHandler) {
815 int index = mPendingEvents.indexOfKey(seq);
817 return; // spurious, event already finished or timed out
820 p = mPendingEvents.valueAt(index);
821 mPendingEvents.removeAt(index);
824 Log.w(TAG, "Timeout waiting for seesion to handle input event after "
825 + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
827 mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
831 invokeFinishedInputEventCallback(p, handled);
834 // Assumes the event has already been removed from the queue.
835 void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
836 p.mHandled = handled;
837 if (p.mHandler.getLooper().isCurrentThread()) {
838 // Already running on the callback handler thread so we can send the callback
842 // Post the event to the callback handler thread.
843 // In this case, the callback will be responsible for recycling the event.
844 Message msg = Message.obtain(p.mHandler, p);
845 msg.setAsynchronous(true);
850 private void flushPendingEventsLocked() {
851 mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
853 final int count = mPendingEvents.size();
854 for (int i = 0; i < count; i++) {
855 int seq = mPendingEvents.keyAt(i);
856 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
857 msg.setAsynchronous(true);
862 private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
863 FinishedInputEventCallback callback, Handler handler) {
864 PendingEvent p = mPendingEventPool.acquire();
866 p = new PendingEvent();
870 p.mCallback = callback;
871 p.mHandler = handler;
875 private void recyclePendingEventLocked(PendingEvent p) {
877 mPendingEventPool.release(p);
880 private void releaseInternal() {
882 synchronized (mHandler) {
883 if (mChannel != null) {
884 if (mSender != null) {
885 flushPendingEventsLocked();
893 synchronized (mSessionCallbackRecordMap) {
894 mSessionCallbackRecordMap.remove(mSeq);
898 private final class InputEventHandler extends Handler {
899 public static final int MSG_SEND_INPUT_EVENT = 1;
900 public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
901 public static final int MSG_FLUSH_INPUT_EVENT = 3;
903 InputEventHandler(Looper looper) {
904 super(looper, null, true);
908 public void handleMessage(Message msg) {
910 case MSG_SEND_INPUT_EVENT: {
911 sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
914 case MSG_TIMEOUT_INPUT_EVENT: {
915 finishedInputEvent(msg.arg1, false, true);
918 case MSG_FLUSH_INPUT_EVENT: {
919 finishedInputEvent(msg.arg1, false, false);
926 private final class TvInputEventSender extends InputEventSender {
927 public TvInputEventSender(InputChannel inputChannel, Looper looper) {
928 super(inputChannel, looper);
932 public void onInputEventFinished(int seq, boolean handled) {
933 finishedInputEvent(seq, handled, false);
937 private final class PendingEvent implements Runnable {
938 public InputEvent mEvent;
939 public Object mToken;
940 public FinishedInputEventCallback mCallback;
941 public Handler mHandler;
942 public boolean mHandled;
944 public void recycle() {
954 mCallback.onFinishedInputEvent(mToken, mHandled);
956 synchronized (mHandler) {
957 recyclePendingEventLocked(this);