Loading api/current.txt +15 −1 Original line number Diff line number Diff line Loading @@ -28163,12 +28163,19 @@ package android.tv { field public static final java.lang.String SERVICE_INTERFACE = "android.tv.TvInputService"; } public abstract class TvInputService.TvInputSessionImpl { public abstract class TvInputService.TvInputSessionImpl implements android.view.KeyEvent.Callback { ctor public TvInputService.TvInputSessionImpl(); method public android.view.View onCreateOverlayView(); method public boolean onGenericMotionEvent(android.view.MotionEvent); method public boolean onKeyDown(int, android.view.KeyEvent); method public boolean onKeyLongPress(int, android.view.KeyEvent); method public boolean onKeyMultiple(int, int, android.view.KeyEvent); method public boolean onKeyUp(int, android.view.KeyEvent); method public abstract void onRelease(); method public abstract boolean onSetSurface(android.view.Surface); method public abstract void onSetVolume(float); method public boolean onTouchEvent(android.view.MotionEvent); method public boolean onTrackballEvent(android.view.MotionEvent); method public abstract boolean onTune(android.net.Uri); method public void setOverlayViewEnabled(boolean); } Loading @@ -28178,9 +28185,16 @@ package android.tv { ctor public TvView(android.content.Context, android.util.AttributeSet); ctor public TvView(android.content.Context, android.util.AttributeSet, int); method public void bindTvInput(android.content.ComponentName, android.tv.TvInputManager.SessionCreateCallback); method public boolean dispatchUnhandledInputEvent(android.view.InputEvent); method public boolean onUnhandledInputEvent(android.view.InputEvent); method public void setOnUnhandledInputEventListener(android.tv.TvView.OnUnhandledInputEventListener); method public void unbindTvInput(); } public static abstract interface TvView.OnUnhandledInputEventListener { method public abstract boolean onUnhandledInputEvent(android.view.InputEvent); } } package android.util { core/java/android/tv/ITvInputClient.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.tv; import android.content.ComponentName; import android.tv.ITvInputSession; import android.view.InputChannel; /** * Interface a client of the ITvInputManager implements, to identify itself and receive information Loading @@ -25,6 +26,6 @@ import android.tv.ITvInputSession; * @hide */ oneway interface ITvInputClient { void onSessionCreated(in ComponentName name, IBinder token, int seq); void onSessionCreated(in ComponentName name, IBinder token, in InputChannel channel, int seq); void onAvailabilityChanged(in ComponentName name, boolean isAvailable); } core/java/android/tv/ITvInputService.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.tv; import android.tv.ITvInputServiceCallback; import android.tv.ITvInputSessionCallback; import android.view.InputChannel; /** * Top-level interface to a TV input component (implemented in a Service). Loading @@ -26,5 +27,5 @@ import android.tv.ITvInputSessionCallback; oneway interface ITvInputService { void registerCallback(ITvInputServiceCallback callback); void unregisterCallback(in ITvInputServiceCallback callback); void createSession(ITvInputSessionCallback callback); void createSession(in InputChannel channel, ITvInputSessionCallback callback); } core/java/android/tv/ITvInputSessionWrapper.java +55 −12 Original line number Diff line number Diff line Loading @@ -20,9 +20,16 @@ import android.content.Context; import android.graphics.Rect; import android.net.Uri; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.tv.TvInputManager.Session; import android.tv.TvInputService.TvInputSessionImpl; import android.util.Log; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import com.android.internal.os.HandlerCaller; Loading @@ -45,50 +52,66 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_RELAYOUT_OVERLAY_VIEW = 6; private static final int DO_REMOVE_OVERLAY_VIEW = 7; private TvInputSessionImpl mTvInputSession; private final HandlerCaller mCaller; public ITvInputSessionWrapper(Context context, TvInputSessionImpl session) { private TvInputSessionImpl mTvInputSessionImpl; private InputChannel mChannel; private TvInputEventReceiver mReceiver; public ITvInputSessionWrapper(Context context, TvInputSessionImpl sessionImpl, InputChannel channel) { mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */); mTvInputSession = session; mTvInputSessionImpl = sessionImpl; mChannel = channel; if (channel != null) { mReceiver = new TvInputEventReceiver(channel, context.getMainLooper()); } } @Override public void executeMessage(Message msg) { if (mTvInputSession == null) { if (mTvInputSessionImpl == null) { return; } switch (msg.what) { case DO_RELEASE: { mTvInputSession.release(); mTvInputSession = null; mTvInputSessionImpl.release(); mTvInputSessionImpl = null; if (mReceiver != null) { mReceiver.dispose(); mReceiver = null; } if (mChannel != null) { mChannel.dispose(); mChannel = null; } return; } case DO_SET_SURFACE: { mTvInputSession.setSurface((Surface) msg.obj); mTvInputSessionImpl.setSurface((Surface) msg.obj); return; } case DO_SET_VOLUME: { mTvInputSession.setVolume((Float) msg.obj); mTvInputSessionImpl.setVolume((Float) msg.obj); return; } case DO_TUNE: { mTvInputSession.tune((Uri) msg.obj); mTvInputSessionImpl.tune((Uri) msg.obj); return; } case DO_CREATE_OVERLAY_VIEW: { SomeArgs args = (SomeArgs) msg.obj; mTvInputSession.createOverlayView((IBinder) args.arg1, (Rect) args.arg2); mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2); args.recycle(); return; } case DO_RELAYOUT_OVERLAY_VIEW: { mTvInputSession.relayoutOverlayView((Rect) msg.obj); mTvInputSessionImpl.relayoutOverlayView((Rect) msg.obj); return; } case DO_REMOVE_OVERLAY_VIEW: { mTvInputSession.removeOverlayView(true); mTvInputSessionImpl.removeOverlayView(true); return; } default: { Loading Loading @@ -133,4 +156,24 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand public void removeOverlayView() { mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW)); } private final class TvInputEventReceiver extends InputEventReceiver { public TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @Override public void onInputEvent(InputEvent event) { if (mTvInputSessionImpl == null) { // The session has been finished. finishInputEvent(event, false); return; } int handled = mTvInputSessionImpl.dispatchInputEvent(event, this); if (handled != Session.DISPATCH_IN_PROGRESS) { finishInputEvent(event, handled == Session.DISPATCH_HANDLED); } } } } core/java/android/tv/TvInputManager.java +263 −3 Original line number Diff line number Diff line Loading @@ -21,9 +21,16 @@ import android.graphics.Rect; import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; import android.util.Pools.Pool; import android.util.Pools.SimplePool; import android.util.SparseArray; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; import android.view.Surface; import android.view.View; Loading Loading @@ -138,7 +145,8 @@ public final class TvInputManager { mUserId = userId; mClient = new ITvInputClient.Stub() { @Override public void onSessionCreated(ComponentName name, IBinder token, int seq) { public void onSessionCreated(ComponentName name, IBinder token, InputChannel channel, int seq) { synchronized (mSessionCreateCallbackRecordMap) { SessionCreateCallbackRecord record = mSessionCreateCallbackRecordMap.get(seq); mSessionCreateCallbackRecordMap.delete(seq); Loading @@ -148,7 +156,7 @@ public final class TvInputManager { } Session session = null; if (token != null) { session = new Session(name, token, mService, mUserId); session = new Session(token, channel, mService, mUserId); } record.postSessionCreated(session); } Loading Loading @@ -321,13 +329,30 @@ public final class TvInputManager { /** The Session provides the per-session functionality of TV inputs. */ public static final class Session { static final int DISPATCH_IN_PROGRESS = -1; static final int DISPATCH_NOT_HANDLED = 0; static final int DISPATCH_HANDLED = 1; private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500; private final ITvInputManager mService; private final int mUserId; // For scheduling input event handling on the main thread. This also serves as a lock to // protect pending input events and the input channel. private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper()); private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20); private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20); private IBinder mToken; private TvInputEventSender mSender; private InputChannel mChannel; /** @hide */ private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) { private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId) { mToken = token; mChannel = channel; mService = service; mUserId = userId; } Loading @@ -347,6 +372,18 @@ public final class TvInputManager { } catch (RemoteException e) { throw new RuntimeException(e); } synchronized (mHandler) { if (mChannel != null) { if (mSender != null) { flushPendingEventsLocked(); mSender.dispose(); mSender = null; } mChannel.dispose(); mChannel = null; } } } /** Loading Loading @@ -478,5 +515,228 @@ public final class TvInputManager { throw new RuntimeException(e); } } /** * Dispatches an input event to this session. * * @param event {@link InputEvent} to dispatch. * @param token A token used to identify the input event later in the callback. * @param callback A callback used to receive the dispatch result. * @param handler {@link Handler} that the dispatch result will be delivered to. * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will * be invoked later. * @throws IllegalArgumentException if any of the necessary arguments is {@code null}. * @hide */ public int dispatchInputEvent(InputEvent event, Object token, FinishedInputEventCallback callback, Handler handler) { if (event == null) { throw new IllegalArgumentException("event cannot be null"); } if (callback != null && handler == null) { throw new IllegalArgumentException("handler cannot be null"); } synchronized (mHandler) { if (mChannel == null) { return DISPATCH_NOT_HANDLED; } PendingEvent p = obtainPendingEventLocked(event, token, callback, handler); if (Looper.myLooper() == Looper.getMainLooper()) { // Already running on the main thread so we can send the event immediately. return sendInputEventOnMainLooperLocked(p); } // Post the event to the main thread. Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p); msg.setAsynchronous(true); mHandler.sendMessage(msg); return DISPATCH_IN_PROGRESS; } } /** * Callback that is invoked when an input event that was dispatched to this session has been * finished. * * @hide */ public interface FinishedInputEventCallback { /** * Called when the dispatched input event is finished. * * @param token a token passed to {@link #dispatchInputEvent}. * @param handled {@code true} if the dispatched input event was handled properly. * {@code false} otherwise. */ public void onFinishedInputEvent(Object token, boolean handled); } // Must be called on the main looper private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) { synchronized (mHandler) { int result = sendInputEventOnMainLooperLocked(p); if (result == DISPATCH_IN_PROGRESS) { return; } } invokeFinishedInputEventCallback(p, false); } private int sendInputEventOnMainLooperLocked(PendingEvent p) { if (mChannel != null) { if (mSender == null) { mSender = new TvInputEventSender(mChannel, mHandler.getLooper()); } final InputEvent event = p.mEvent; final int seq = event.getSequenceNumber(); if (mSender.sendInputEvent(seq, event)) { mPendingEvents.put(seq, p); Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT); return DISPATCH_IN_PROGRESS; } Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:" + event); } return DISPATCH_NOT_HANDLED; } void finishedInputEvent(int seq, boolean handled, boolean timeout) { final PendingEvent p; synchronized (mHandler) { int index = mPendingEvents.indexOfKey(seq); if (index < 0) { return; // spurious, event already finished or timed out } p = mPendingEvents.valueAt(index); mPendingEvents.removeAt(index); if (timeout) { Log.w(TAG, "Timeout waiting for seesion to handle input event after " + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken); } else { mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); } } invokeFinishedInputEventCallback(p, handled); } // Assumes the event has already been removed from the queue. void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { p.mHandled = handled; if (p.mHandler.getLooper().isCurrentThread()) { // Already running on the callback handler thread so we can send the callback // immediately. p.run(); } else { // Post the event to the callback handler thread. // In this case, the callback will be responsible for recycling the event. Message msg = Message.obtain(p.mHandler, p); msg.setAsynchronous(true); msg.sendToTarget(); } } private void flushPendingEventsLocked() { mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT); final int count = mPendingEvents.size(); for (int i = 0; i < count; i++) { int seq = mPendingEvents.keyAt(i); Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0); msg.setAsynchronous(true); msg.sendToTarget(); } } private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, FinishedInputEventCallback callback, Handler handler) { PendingEvent p = mPendingEventPool.acquire(); if (p == null) { p = new PendingEvent(); } p.mEvent = event; p.mToken = token; p.mCallback = callback; p.mHandler = handler; return p; } private void recyclePendingEventLocked(PendingEvent p) { p.recycle(); mPendingEventPool.release(p); } private final class InputEventHandler extends Handler { public static final int MSG_SEND_INPUT_EVENT = 1; public static final int MSG_TIMEOUT_INPUT_EVENT = 2; public static final int MSG_FLUSH_INPUT_EVENT = 3; InputEventHandler(Looper looper) { super(looper, null, true); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SEND_INPUT_EVENT: { sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj); return; } case MSG_TIMEOUT_INPUT_EVENT: { finishedInputEvent(msg.arg1, false, true); return; } case MSG_FLUSH_INPUT_EVENT: { finishedInputEvent(msg.arg1, false, false); return; } } } } private final class TvInputEventSender extends InputEventSender { public TvInputEventSender(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @Override public void onInputEventFinished(int seq, boolean handled) { finishedInputEvent(seq, handled, false); } } private final class PendingEvent implements Runnable { public InputEvent mEvent; public Object mToken; public FinishedInputEventCallback mCallback; public Handler mHandler; public boolean mHandled; public void recycle() { mEvent = null; mToken = null; mCallback = null; mHandler = null; mHandled = false; } @Override public void run() { mCallback.onFinishedInputEvent(mToken, mHandled); synchronized (mHandler) { recyclePendingEventLocked(this); } } } } } Loading
api/current.txt +15 −1 Original line number Diff line number Diff line Loading @@ -28163,12 +28163,19 @@ package android.tv { field public static final java.lang.String SERVICE_INTERFACE = "android.tv.TvInputService"; } public abstract class TvInputService.TvInputSessionImpl { public abstract class TvInputService.TvInputSessionImpl implements android.view.KeyEvent.Callback { ctor public TvInputService.TvInputSessionImpl(); method public android.view.View onCreateOverlayView(); method public boolean onGenericMotionEvent(android.view.MotionEvent); method public boolean onKeyDown(int, android.view.KeyEvent); method public boolean onKeyLongPress(int, android.view.KeyEvent); method public boolean onKeyMultiple(int, int, android.view.KeyEvent); method public boolean onKeyUp(int, android.view.KeyEvent); method public abstract void onRelease(); method public abstract boolean onSetSurface(android.view.Surface); method public abstract void onSetVolume(float); method public boolean onTouchEvent(android.view.MotionEvent); method public boolean onTrackballEvent(android.view.MotionEvent); method public abstract boolean onTune(android.net.Uri); method public void setOverlayViewEnabled(boolean); } Loading @@ -28178,9 +28185,16 @@ package android.tv { ctor public TvView(android.content.Context, android.util.AttributeSet); ctor public TvView(android.content.Context, android.util.AttributeSet, int); method public void bindTvInput(android.content.ComponentName, android.tv.TvInputManager.SessionCreateCallback); method public boolean dispatchUnhandledInputEvent(android.view.InputEvent); method public boolean onUnhandledInputEvent(android.view.InputEvent); method public void setOnUnhandledInputEventListener(android.tv.TvView.OnUnhandledInputEventListener); method public void unbindTvInput(); } public static abstract interface TvView.OnUnhandledInputEventListener { method public abstract boolean onUnhandledInputEvent(android.view.InputEvent); } } package android.util {
core/java/android/tv/ITvInputClient.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.tv; import android.content.ComponentName; import android.tv.ITvInputSession; import android.view.InputChannel; /** * Interface a client of the ITvInputManager implements, to identify itself and receive information Loading @@ -25,6 +26,6 @@ import android.tv.ITvInputSession; * @hide */ oneway interface ITvInputClient { void onSessionCreated(in ComponentName name, IBinder token, int seq); void onSessionCreated(in ComponentName name, IBinder token, in InputChannel channel, int seq); void onAvailabilityChanged(in ComponentName name, boolean isAvailable); }
core/java/android/tv/ITvInputService.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.tv; import android.tv.ITvInputServiceCallback; import android.tv.ITvInputSessionCallback; import android.view.InputChannel; /** * Top-level interface to a TV input component (implemented in a Service). Loading @@ -26,5 +27,5 @@ import android.tv.ITvInputSessionCallback; oneway interface ITvInputService { void registerCallback(ITvInputServiceCallback callback); void unregisterCallback(in ITvInputServiceCallback callback); void createSession(ITvInputSessionCallback callback); void createSession(in InputChannel channel, ITvInputSessionCallback callback); }
core/java/android/tv/ITvInputSessionWrapper.java +55 −12 Original line number Diff line number Diff line Loading @@ -20,9 +20,16 @@ import android.content.Context; import android.graphics.Rect; import android.net.Uri; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.tv.TvInputManager.Session; import android.tv.TvInputService.TvInputSessionImpl; import android.util.Log; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import com.android.internal.os.HandlerCaller; Loading @@ -45,50 +52,66 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_RELAYOUT_OVERLAY_VIEW = 6; private static final int DO_REMOVE_OVERLAY_VIEW = 7; private TvInputSessionImpl mTvInputSession; private final HandlerCaller mCaller; public ITvInputSessionWrapper(Context context, TvInputSessionImpl session) { private TvInputSessionImpl mTvInputSessionImpl; private InputChannel mChannel; private TvInputEventReceiver mReceiver; public ITvInputSessionWrapper(Context context, TvInputSessionImpl sessionImpl, InputChannel channel) { mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */); mTvInputSession = session; mTvInputSessionImpl = sessionImpl; mChannel = channel; if (channel != null) { mReceiver = new TvInputEventReceiver(channel, context.getMainLooper()); } } @Override public void executeMessage(Message msg) { if (mTvInputSession == null) { if (mTvInputSessionImpl == null) { return; } switch (msg.what) { case DO_RELEASE: { mTvInputSession.release(); mTvInputSession = null; mTvInputSessionImpl.release(); mTvInputSessionImpl = null; if (mReceiver != null) { mReceiver.dispose(); mReceiver = null; } if (mChannel != null) { mChannel.dispose(); mChannel = null; } return; } case DO_SET_SURFACE: { mTvInputSession.setSurface((Surface) msg.obj); mTvInputSessionImpl.setSurface((Surface) msg.obj); return; } case DO_SET_VOLUME: { mTvInputSession.setVolume((Float) msg.obj); mTvInputSessionImpl.setVolume((Float) msg.obj); return; } case DO_TUNE: { mTvInputSession.tune((Uri) msg.obj); mTvInputSessionImpl.tune((Uri) msg.obj); return; } case DO_CREATE_OVERLAY_VIEW: { SomeArgs args = (SomeArgs) msg.obj; mTvInputSession.createOverlayView((IBinder) args.arg1, (Rect) args.arg2); mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2); args.recycle(); return; } case DO_RELAYOUT_OVERLAY_VIEW: { mTvInputSession.relayoutOverlayView((Rect) msg.obj); mTvInputSessionImpl.relayoutOverlayView((Rect) msg.obj); return; } case DO_REMOVE_OVERLAY_VIEW: { mTvInputSession.removeOverlayView(true); mTvInputSessionImpl.removeOverlayView(true); return; } default: { Loading Loading @@ -133,4 +156,24 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand public void removeOverlayView() { mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW)); } private final class TvInputEventReceiver extends InputEventReceiver { public TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @Override public void onInputEvent(InputEvent event) { if (mTvInputSessionImpl == null) { // The session has been finished. finishInputEvent(event, false); return; } int handled = mTvInputSessionImpl.dispatchInputEvent(event, this); if (handled != Session.DISPATCH_IN_PROGRESS) { finishInputEvent(event, handled == Session.DISPATCH_HANDLED); } } } }
core/java/android/tv/TvInputManager.java +263 −3 Original line number Diff line number Diff line Loading @@ -21,9 +21,16 @@ import android.graphics.Rect; import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; import android.util.Pools.Pool; import android.util.Pools.SimplePool; import android.util.SparseArray; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; import android.view.Surface; import android.view.View; Loading Loading @@ -138,7 +145,8 @@ public final class TvInputManager { mUserId = userId; mClient = new ITvInputClient.Stub() { @Override public void onSessionCreated(ComponentName name, IBinder token, int seq) { public void onSessionCreated(ComponentName name, IBinder token, InputChannel channel, int seq) { synchronized (mSessionCreateCallbackRecordMap) { SessionCreateCallbackRecord record = mSessionCreateCallbackRecordMap.get(seq); mSessionCreateCallbackRecordMap.delete(seq); Loading @@ -148,7 +156,7 @@ public final class TvInputManager { } Session session = null; if (token != null) { session = new Session(name, token, mService, mUserId); session = new Session(token, channel, mService, mUserId); } record.postSessionCreated(session); } Loading Loading @@ -321,13 +329,30 @@ public final class TvInputManager { /** The Session provides the per-session functionality of TV inputs. */ public static final class Session { static final int DISPATCH_IN_PROGRESS = -1; static final int DISPATCH_NOT_HANDLED = 0; static final int DISPATCH_HANDLED = 1; private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500; private final ITvInputManager mService; private final int mUserId; // For scheduling input event handling on the main thread. This also serves as a lock to // protect pending input events and the input channel. private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper()); private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20); private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20); private IBinder mToken; private TvInputEventSender mSender; private InputChannel mChannel; /** @hide */ private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) { private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId) { mToken = token; mChannel = channel; mService = service; mUserId = userId; } Loading @@ -347,6 +372,18 @@ public final class TvInputManager { } catch (RemoteException e) { throw new RuntimeException(e); } synchronized (mHandler) { if (mChannel != null) { if (mSender != null) { flushPendingEventsLocked(); mSender.dispose(); mSender = null; } mChannel.dispose(); mChannel = null; } } } /** Loading Loading @@ -478,5 +515,228 @@ public final class TvInputManager { throw new RuntimeException(e); } } /** * Dispatches an input event to this session. * * @param event {@link InputEvent} to dispatch. * @param token A token used to identify the input event later in the callback. * @param callback A callback used to receive the dispatch result. * @param handler {@link Handler} that the dispatch result will be delivered to. * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will * be invoked later. * @throws IllegalArgumentException if any of the necessary arguments is {@code null}. * @hide */ public int dispatchInputEvent(InputEvent event, Object token, FinishedInputEventCallback callback, Handler handler) { if (event == null) { throw new IllegalArgumentException("event cannot be null"); } if (callback != null && handler == null) { throw new IllegalArgumentException("handler cannot be null"); } synchronized (mHandler) { if (mChannel == null) { return DISPATCH_NOT_HANDLED; } PendingEvent p = obtainPendingEventLocked(event, token, callback, handler); if (Looper.myLooper() == Looper.getMainLooper()) { // Already running on the main thread so we can send the event immediately. return sendInputEventOnMainLooperLocked(p); } // Post the event to the main thread. Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p); msg.setAsynchronous(true); mHandler.sendMessage(msg); return DISPATCH_IN_PROGRESS; } } /** * Callback that is invoked when an input event that was dispatched to this session has been * finished. * * @hide */ public interface FinishedInputEventCallback { /** * Called when the dispatched input event is finished. * * @param token a token passed to {@link #dispatchInputEvent}. * @param handled {@code true} if the dispatched input event was handled properly. * {@code false} otherwise. */ public void onFinishedInputEvent(Object token, boolean handled); } // Must be called on the main looper private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) { synchronized (mHandler) { int result = sendInputEventOnMainLooperLocked(p); if (result == DISPATCH_IN_PROGRESS) { return; } } invokeFinishedInputEventCallback(p, false); } private int sendInputEventOnMainLooperLocked(PendingEvent p) { if (mChannel != null) { if (mSender == null) { mSender = new TvInputEventSender(mChannel, mHandler.getLooper()); } final InputEvent event = p.mEvent; final int seq = event.getSequenceNumber(); if (mSender.sendInputEvent(seq, event)) { mPendingEvents.put(seq, p); Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT); return DISPATCH_IN_PROGRESS; } Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:" + event); } return DISPATCH_NOT_HANDLED; } void finishedInputEvent(int seq, boolean handled, boolean timeout) { final PendingEvent p; synchronized (mHandler) { int index = mPendingEvents.indexOfKey(seq); if (index < 0) { return; // spurious, event already finished or timed out } p = mPendingEvents.valueAt(index); mPendingEvents.removeAt(index); if (timeout) { Log.w(TAG, "Timeout waiting for seesion to handle input event after " + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken); } else { mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); } } invokeFinishedInputEventCallback(p, handled); } // Assumes the event has already been removed from the queue. void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { p.mHandled = handled; if (p.mHandler.getLooper().isCurrentThread()) { // Already running on the callback handler thread so we can send the callback // immediately. p.run(); } else { // Post the event to the callback handler thread. // In this case, the callback will be responsible for recycling the event. Message msg = Message.obtain(p.mHandler, p); msg.setAsynchronous(true); msg.sendToTarget(); } } private void flushPendingEventsLocked() { mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT); final int count = mPendingEvents.size(); for (int i = 0; i < count; i++) { int seq = mPendingEvents.keyAt(i); Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0); msg.setAsynchronous(true); msg.sendToTarget(); } } private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, FinishedInputEventCallback callback, Handler handler) { PendingEvent p = mPendingEventPool.acquire(); if (p == null) { p = new PendingEvent(); } p.mEvent = event; p.mToken = token; p.mCallback = callback; p.mHandler = handler; return p; } private void recyclePendingEventLocked(PendingEvent p) { p.recycle(); mPendingEventPool.release(p); } private final class InputEventHandler extends Handler { public static final int MSG_SEND_INPUT_EVENT = 1; public static final int MSG_TIMEOUT_INPUT_EVENT = 2; public static final int MSG_FLUSH_INPUT_EVENT = 3; InputEventHandler(Looper looper) { super(looper, null, true); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SEND_INPUT_EVENT: { sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj); return; } case MSG_TIMEOUT_INPUT_EVENT: { finishedInputEvent(msg.arg1, false, true); return; } case MSG_FLUSH_INPUT_EVENT: { finishedInputEvent(msg.arg1, false, false); return; } } } } private final class TvInputEventSender extends InputEventSender { public TvInputEventSender(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @Override public void onInputEventFinished(int seq, boolean handled) { finishedInputEvent(seq, handled, false); } } private final class PendingEvent implements Runnable { public InputEvent mEvent; public Object mToken; public FinishedInputEventCallback mCallback; public Handler mHandler; public boolean mHandled; public void recycle() { mEvent = null; mToken = null; mCallback = null; mHandler = null; mHandled = false; } @Override public void run() { mCallback.onFinishedInputEvent(mToken, mHandled); synchronized (mHandler) { recyclePendingEventLocked(this); } } } } }