Loading media/java/android/media/tv/interactive/ITvIAppClient.aidl +3 −1 Original line number Diff line number Diff line Loading @@ -16,13 +16,15 @@ package android.media.tv.interactive; import android.view.InputChannel; /** * Interface a client of the ITvIAppManager implements, to identify itself and receive information * about changes to the state of each TV interactive application service. * @hide */ oneway interface ITvIAppClient { void onSessionCreated(in String iAppServiceId, IBinder token, int seq); void onSessionCreated(in String iAppServiceId, IBinder token, in InputChannel channel, int seq); void onSessionReleased(int seq); void onLayoutSurface(int left, int top, int right, int bottom, int seq); } No newline at end of file media/java/android/media/tv/interactive/ITvIAppService.aidl +3 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.media.tv.interactive; import android.media.tv.interactive.ITvIAppServiceCallback; import android.media.tv.interactive.ITvIAppSessionCallback; import android.view.InputChannel; /** * Top-level interface to a TV IApp component (implemented in a Service). It's used for Loading @@ -27,5 +28,6 @@ import android.media.tv.interactive.ITvIAppSessionCallback; oneway interface ITvIAppService { void registerCallback(in ITvIAppServiceCallback callback); void unregisterCallback(in ITvIAppServiceCallback callback); void createSession(in ITvIAppSessionCallback callback, in String iAppServiceId, int type); void createSession(in InputChannel channel, in ITvIAppSessionCallback callback, in String iAppServiceId, int type); } No newline at end of file media/java/android/media/tv/interactive/TvIAppManager.java +260 −6 Original line number Diff line number Diff line Loading @@ -23,9 +23,15 @@ import android.content.Context; import android.media.tv.TvInputManager; 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; import android.util.SparseArray; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; import android.view.Surface; import com.android.internal.util.Preconditions; Loading Loading @@ -67,8 +73,8 @@ public final class TvIAppManager { mUserId = userId; mClient = new ITvIAppClient.Stub() { @Override public void onSessionCreated(String iAppServiceId, IBinder token, int seq) { // TODO: use InputChannel for input events public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { Loading @@ -77,7 +83,7 @@ public final class TvIAppManager { } Session session = null; if (token != null) { session = new Session(token, mService, mUserId, seq, session = new Session(token, channel, mService, mUserId, seq, mSessionCallbackRecordMap); } else { mSessionCallbackRecordMap.delete(seq); Loading Loading @@ -351,18 +357,33 @@ public final class TvIAppManager { * @hide */ 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 ITvIAppManager mService; private final int mUserId; private final int mSeq; private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; private IBinder mToken; // 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 TvInputManager.Session mInputSession; private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20); private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20); private Session(IBinder token, ITvIAppManager service, int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { private IBinder mToken; private TvInputEventSender mSender; private InputChannel mInputChannel; private Session(IBinder token, InputChannel channel, ITvIAppManager service, int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { mToken = token; mInputChannel = channel; mService = service; mUserId = userId; mSeq = seq; Loading Loading @@ -427,6 +448,43 @@ public final class TvIAppManager { } } /** * Dispatches an input event to this session. * * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}. * @param token A token used to identify the input event later in the callback. * @param callback A callback used to receive the dispatch result. Cannot be {@code null}. * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be * {@code null}. * @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. * @hide */ public int dispatchInputEvent(@NonNull InputEvent event, Object token, @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) { Preconditions.checkNotNull(event); Preconditions.checkNotNull(callback); Preconditions.checkNotNull(handler); synchronized (mHandler) { if (mInputChannel == 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; } } /** * Releases this session. */ Loading @@ -444,12 +502,208 @@ public final class TvIAppManager { releaseInternal(); } 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 void releaseInternal() { mToken = null; synchronized (mHandler) { if (mInputChannel != null) { if (mSender != null) { flushPendingEventsLocked(); mSender.dispose(); mSender = null; } mInputChannel.dispose(); mInputChannel = null; } } synchronized (mSessionCallbackRecordMap) { mSessionCallbackRecordMap.delete(mSeq); } } 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.mEventToken = token; p.mCallback = callback; p.mEventHandler = handler; return p; } // Assumes the event has already been removed from the queue. void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { p.mHandled = handled; if (p.mEventHandler.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.mEventHandler, p); msg.setAsynchronous(true); msg.sendToTarget(); } } // 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 (mInputChannel != null) { if (mSender == null) { mSender = new TvInputEventSender(mInputChannel, 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 session to handle input event after " + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken); } else { mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); } } invokeFinishedInputEventCallback(p, handled); } private void recyclePendingEventLocked(PendingEvent p) { p.recycle(); mPendingEventPool.release(p); } /** * 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. */ void onFinishedInputEvent(Object token, boolean handled); } 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 { 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 mEventToken; public FinishedInputEventCallback mCallback; public Handler mEventHandler; public boolean mHandled; public void recycle() { mEvent = null; mEventToken = null; mCallback = null; mEventHandler = null; mHandled = false; } @Override public void run() { mCallback.onFinishedInputEvent(mEventToken, mHandled); synchronized (mEventHandler) { recyclePendingEventLocked(this); } } } } private static final class SessionCallbackRecord { Loading media/java/android/media/tv/interactive/TvIAppService.java +135 −9 Original line number Diff line number Diff line Loading @@ -25,11 +25,17 @@ import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; import android.view.InputChannel; import android.view.InputDevice; 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.SomeArgs; Loading Loading @@ -85,14 +91,16 @@ public abstract class TvIAppService extends Service { } @Override public void createSession(ITvIAppSessionCallback cb, String iAppServiceId, int type) { public void createSession(InputChannel channel, ITvIAppSessionCallback cb, String iAppServiceId, int type) { if (cb == null) { return; } SomeArgs args = SomeArgs.obtain(); args.arg1 = cb; args.arg2 = iAppServiceId; args.arg3 = type; args.arg1 = channel; args.arg2 = cb; args.arg3 = iAppServiceId; args.arg4 = type; mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args) .sendToTarget(); } Loading Loading @@ -122,6 +130,8 @@ public abstract class TvIAppService extends Service { * @hide */ public abstract static class Session implements KeyEvent.Callback { private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); private final Object mLock = new Object(); // @GuardedBy("mLock") private ITvIAppSessionCallback mSessionCallback; Loading Loading @@ -181,6 +191,60 @@ public abstract class TvIAppService extends Service { public void onRelease() { } /** * TODO: JavaDoc of APIs related to input events. * @hide */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return false; } /** * @hide */ @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { return false; } /** * @hide */ @Override public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { return false; } /** * @hide */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return false; } /** * @hide */ public boolean onTouchEvent(MotionEvent event) { return false; } /** * @hide */ public boolean onTrackballEvent(MotionEvent event) { return false; } /** * @hide */ public boolean onGenericMotionEvent(MotionEvent event) { return false; } /** * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position * is relative to the overlay view that sits on top of this surface. Loading Loading @@ -226,6 +290,39 @@ public abstract class TvIAppService extends Service { } } /** * Takes care of dispatching incoming input events and tells whether the event was handled. */ int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) { if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")"); if (event instanceof KeyEvent) { KeyEvent keyEvent = (KeyEvent) event; if (keyEvent.dispatch(this, mDispatcherState, this)) { return TvIAppManager.Session.DISPATCH_HANDLED; } // TODO: special handlings of navigation keys and media keys } else if (event instanceof MotionEvent) { MotionEvent motionEvent = (MotionEvent) event; final int source = motionEvent.getSource(); if (motionEvent.isTouchEvent()) { if (onTouchEvent(motionEvent)) { return TvIAppManager.Session.DISPATCH_HANDLED; } } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { if (onTrackballEvent(motionEvent)) { return TvIAppManager.Session.DISPATCH_HANDLED; } } else { if (onGenericMotionEvent(motionEvent)) { return TvIAppManager.Session.DISPATCH_HANDLED; } } } // TODO: handle overlay view return TvIAppManager.Session.DISPATCH_NOT_HANDLED; } private void initialize(ITvIAppSessionCallback callback) { synchronized (mLock) { mSessionCallback = callback; Loading Loading @@ -281,10 +378,17 @@ public abstract class TvIAppService extends Service { * @hide */ public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub { // TODO: put ITvIAppSessionWrapper in a separate Java file private final Session mSessionImpl; private InputChannel mChannel; private TvIAppEventReceiver mReceiver; public ITvIAppSessionWrapper(Session mSessionImpl) { public ITvIAppSessionWrapper(Context context, Session mSessionImpl, InputChannel channel) { this.mSessionImpl = mSessionImpl; mChannel = channel; if (channel != null) { mReceiver = new TvIAppEventReceiver(channel, context.getMainLooper()); } } @Override Loading @@ -306,6 +410,26 @@ public abstract class TvIAppService extends Service { public void dispatchSurfaceChanged(int format, int width, int height) { mSessionImpl.dispatchSurfaceChanged(format, width, height); } private final class TvIAppEventReceiver extends InputEventReceiver { TvIAppEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @Override public void onInputEvent(InputEvent event) { if (mSessionImpl == null) { // The session has been finished. finishInputEvent(event, false); return; } int handled = mSessionImpl.dispatchInputEvent(event, this); if (handled != TvIAppManager.Session.DISPATCH_IN_PROGRESS) { finishInputEvent(event, handled == TvIAppManager.Session.DISPATCH_HANDLED); } } } } @SuppressLint("HandlerLeak") Loading @@ -318,9 +442,10 @@ public abstract class TvIAppService extends Service { switch (msg.what) { case DO_CREATE_SESSION: { SomeArgs args = (SomeArgs) msg.obj; ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg1; String iAppServiceId = (String) args.arg2; int type = (int) args.arg3; InputChannel channel = (InputChannel) args.arg1; ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg2; String iAppServiceId = (String) args.arg3; int type = (int) args.arg4; args.recycle(); Session sessionImpl = onCreateSession(iAppServiceId, type); if (sessionImpl == null) { Loading @@ -332,7 +457,8 @@ public abstract class TvIAppService extends Service { } return; } ITvIAppSession stub = new ITvIAppSessionWrapper(sessionImpl); ITvIAppSession stub = new ITvIAppSessionWrapper( TvIAppService.this, sessionImpl, channel); SomeArgs someArgs = SomeArgs.obtain(); someArgs.arg1 = sessionImpl; Loading services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +19 −11 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
media/java/android/media/tv/interactive/ITvIAppClient.aidl +3 −1 Original line number Diff line number Diff line Loading @@ -16,13 +16,15 @@ package android.media.tv.interactive; import android.view.InputChannel; /** * Interface a client of the ITvIAppManager implements, to identify itself and receive information * about changes to the state of each TV interactive application service. * @hide */ oneway interface ITvIAppClient { void onSessionCreated(in String iAppServiceId, IBinder token, int seq); void onSessionCreated(in String iAppServiceId, IBinder token, in InputChannel channel, int seq); void onSessionReleased(int seq); void onLayoutSurface(int left, int top, int right, int bottom, int seq); } No newline at end of file
media/java/android/media/tv/interactive/ITvIAppService.aidl +3 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.media.tv.interactive; import android.media.tv.interactive.ITvIAppServiceCallback; import android.media.tv.interactive.ITvIAppSessionCallback; import android.view.InputChannel; /** * Top-level interface to a TV IApp component (implemented in a Service). It's used for Loading @@ -27,5 +28,6 @@ import android.media.tv.interactive.ITvIAppSessionCallback; oneway interface ITvIAppService { void registerCallback(in ITvIAppServiceCallback callback); void unregisterCallback(in ITvIAppServiceCallback callback); void createSession(in ITvIAppSessionCallback callback, in String iAppServiceId, int type); void createSession(in InputChannel channel, in ITvIAppSessionCallback callback, in String iAppServiceId, int type); } No newline at end of file
media/java/android/media/tv/interactive/TvIAppManager.java +260 −6 Original line number Diff line number Diff line Loading @@ -23,9 +23,15 @@ import android.content.Context; import android.media.tv.TvInputManager; 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; import android.util.SparseArray; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; import android.view.Surface; import com.android.internal.util.Preconditions; Loading Loading @@ -67,8 +73,8 @@ public final class TvIAppManager { mUserId = userId; mClient = new ITvIAppClient.Stub() { @Override public void onSessionCreated(String iAppServiceId, IBinder token, int seq) { // TODO: use InputChannel for input events public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { Loading @@ -77,7 +83,7 @@ public final class TvIAppManager { } Session session = null; if (token != null) { session = new Session(token, mService, mUserId, seq, session = new Session(token, channel, mService, mUserId, seq, mSessionCallbackRecordMap); } else { mSessionCallbackRecordMap.delete(seq); Loading Loading @@ -351,18 +357,33 @@ public final class TvIAppManager { * @hide */ 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 ITvIAppManager mService; private final int mUserId; private final int mSeq; private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; private IBinder mToken; // 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 TvInputManager.Session mInputSession; private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20); private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20); private Session(IBinder token, ITvIAppManager service, int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { private IBinder mToken; private TvInputEventSender mSender; private InputChannel mInputChannel; private Session(IBinder token, InputChannel channel, ITvIAppManager service, int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { mToken = token; mInputChannel = channel; mService = service; mUserId = userId; mSeq = seq; Loading Loading @@ -427,6 +448,43 @@ public final class TvIAppManager { } } /** * Dispatches an input event to this session. * * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}. * @param token A token used to identify the input event later in the callback. * @param callback A callback used to receive the dispatch result. Cannot be {@code null}. * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be * {@code null}. * @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. * @hide */ public int dispatchInputEvent(@NonNull InputEvent event, Object token, @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) { Preconditions.checkNotNull(event); Preconditions.checkNotNull(callback); Preconditions.checkNotNull(handler); synchronized (mHandler) { if (mInputChannel == 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; } } /** * Releases this session. */ Loading @@ -444,12 +502,208 @@ public final class TvIAppManager { releaseInternal(); } 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 void releaseInternal() { mToken = null; synchronized (mHandler) { if (mInputChannel != null) { if (mSender != null) { flushPendingEventsLocked(); mSender.dispose(); mSender = null; } mInputChannel.dispose(); mInputChannel = null; } } synchronized (mSessionCallbackRecordMap) { mSessionCallbackRecordMap.delete(mSeq); } } 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.mEventToken = token; p.mCallback = callback; p.mEventHandler = handler; return p; } // Assumes the event has already been removed from the queue. void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { p.mHandled = handled; if (p.mEventHandler.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.mEventHandler, p); msg.setAsynchronous(true); msg.sendToTarget(); } } // 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 (mInputChannel != null) { if (mSender == null) { mSender = new TvInputEventSender(mInputChannel, 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 session to handle input event after " + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken); } else { mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); } } invokeFinishedInputEventCallback(p, handled); } private void recyclePendingEventLocked(PendingEvent p) { p.recycle(); mPendingEventPool.release(p); } /** * 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. */ void onFinishedInputEvent(Object token, boolean handled); } 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 { 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 mEventToken; public FinishedInputEventCallback mCallback; public Handler mEventHandler; public boolean mHandled; public void recycle() { mEvent = null; mEventToken = null; mCallback = null; mEventHandler = null; mHandled = false; } @Override public void run() { mCallback.onFinishedInputEvent(mEventToken, mHandled); synchronized (mEventHandler) { recyclePendingEventLocked(this); } } } } private static final class SessionCallbackRecord { Loading
media/java/android/media/tv/interactive/TvIAppService.java +135 −9 Original line number Diff line number Diff line Loading @@ -25,11 +25,17 @@ import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; import android.view.InputChannel; import android.view.InputDevice; 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.SomeArgs; Loading Loading @@ -85,14 +91,16 @@ public abstract class TvIAppService extends Service { } @Override public void createSession(ITvIAppSessionCallback cb, String iAppServiceId, int type) { public void createSession(InputChannel channel, ITvIAppSessionCallback cb, String iAppServiceId, int type) { if (cb == null) { return; } SomeArgs args = SomeArgs.obtain(); args.arg1 = cb; args.arg2 = iAppServiceId; args.arg3 = type; args.arg1 = channel; args.arg2 = cb; args.arg3 = iAppServiceId; args.arg4 = type; mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args) .sendToTarget(); } Loading Loading @@ -122,6 +130,8 @@ public abstract class TvIAppService extends Service { * @hide */ public abstract static class Session implements KeyEvent.Callback { private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); private final Object mLock = new Object(); // @GuardedBy("mLock") private ITvIAppSessionCallback mSessionCallback; Loading Loading @@ -181,6 +191,60 @@ public abstract class TvIAppService extends Service { public void onRelease() { } /** * TODO: JavaDoc of APIs related to input events. * @hide */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return false; } /** * @hide */ @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { return false; } /** * @hide */ @Override public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { return false; } /** * @hide */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return false; } /** * @hide */ public boolean onTouchEvent(MotionEvent event) { return false; } /** * @hide */ public boolean onTrackballEvent(MotionEvent event) { return false; } /** * @hide */ public boolean onGenericMotionEvent(MotionEvent event) { return false; } /** * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position * is relative to the overlay view that sits on top of this surface. Loading Loading @@ -226,6 +290,39 @@ public abstract class TvIAppService extends Service { } } /** * Takes care of dispatching incoming input events and tells whether the event was handled. */ int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) { if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")"); if (event instanceof KeyEvent) { KeyEvent keyEvent = (KeyEvent) event; if (keyEvent.dispatch(this, mDispatcherState, this)) { return TvIAppManager.Session.DISPATCH_HANDLED; } // TODO: special handlings of navigation keys and media keys } else if (event instanceof MotionEvent) { MotionEvent motionEvent = (MotionEvent) event; final int source = motionEvent.getSource(); if (motionEvent.isTouchEvent()) { if (onTouchEvent(motionEvent)) { return TvIAppManager.Session.DISPATCH_HANDLED; } } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { if (onTrackballEvent(motionEvent)) { return TvIAppManager.Session.DISPATCH_HANDLED; } } else { if (onGenericMotionEvent(motionEvent)) { return TvIAppManager.Session.DISPATCH_HANDLED; } } } // TODO: handle overlay view return TvIAppManager.Session.DISPATCH_NOT_HANDLED; } private void initialize(ITvIAppSessionCallback callback) { synchronized (mLock) { mSessionCallback = callback; Loading Loading @@ -281,10 +378,17 @@ public abstract class TvIAppService extends Service { * @hide */ public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub { // TODO: put ITvIAppSessionWrapper in a separate Java file private final Session mSessionImpl; private InputChannel mChannel; private TvIAppEventReceiver mReceiver; public ITvIAppSessionWrapper(Session mSessionImpl) { public ITvIAppSessionWrapper(Context context, Session mSessionImpl, InputChannel channel) { this.mSessionImpl = mSessionImpl; mChannel = channel; if (channel != null) { mReceiver = new TvIAppEventReceiver(channel, context.getMainLooper()); } } @Override Loading @@ -306,6 +410,26 @@ public abstract class TvIAppService extends Service { public void dispatchSurfaceChanged(int format, int width, int height) { mSessionImpl.dispatchSurfaceChanged(format, width, height); } private final class TvIAppEventReceiver extends InputEventReceiver { TvIAppEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @Override public void onInputEvent(InputEvent event) { if (mSessionImpl == null) { // The session has been finished. finishInputEvent(event, false); return; } int handled = mSessionImpl.dispatchInputEvent(event, this); if (handled != TvIAppManager.Session.DISPATCH_IN_PROGRESS) { finishInputEvent(event, handled == TvIAppManager.Session.DISPATCH_HANDLED); } } } } @SuppressLint("HandlerLeak") Loading @@ -318,9 +442,10 @@ public abstract class TvIAppService extends Service { switch (msg.what) { case DO_CREATE_SESSION: { SomeArgs args = (SomeArgs) msg.obj; ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg1; String iAppServiceId = (String) args.arg2; int type = (int) args.arg3; InputChannel channel = (InputChannel) args.arg1; ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg2; String iAppServiceId = (String) args.arg3; int type = (int) args.arg4; args.recycle(); Session sessionImpl = onCreateSession(iAppServiceId, type); if (sessionImpl == null) { Loading @@ -332,7 +457,8 @@ public abstract class TvIAppService extends Service { } return; } ITvIAppSession stub = new ITvIAppSessionWrapper(sessionImpl); ITvIAppSession stub = new ITvIAppSessionWrapper( TvIAppService.this, sessionImpl, channel); SomeArgs someArgs = SomeArgs.obtain(); someArgs.arg1 = sessionImpl; Loading
services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +19 −11 File changed.Preview size limit exceeded, changes collapsed. Show changes