diff options
author | Jeff Brown <jeffbrown@google.com> | 2011-12-01 14:01:49 -0800 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2011-12-01 21:04:47 -0800 |
commit | 32cbc3855c2a971aa5a801fd339fb6a37db91a1a (patch) | |
tree | 40d3fcf12181eb6d50fac3a3734ecf3c9f4953ec | |
parent | db918cf171afd3d4b3c22aab6dd3403d1dec94de (diff) | |
download | frameworks_base-32cbc3855c2a971aa5a801fd339fb6a37db91a1a.zip frameworks_base-32cbc3855c2a971aa5a801fd339fb6a37db91a1a.tar.gz frameworks_base-32cbc3855c2a971aa5a801fd339fb6a37db91a1a.tar.bz2 |
Refactor InputQueue as InputEventReceiver.
This change simplifies the code associated with receiving input
events from input channels and makes it more robust. It also
does a better job of ensuring that input events are properly
recycled (sometimes we dropped them on the floor).
This change also adds a sequence number to all events, which is
handy for determining whether we are looking at the same event or a
new one, particularly when events are recycled.
Change-Id: I4ebd88f73b5f77f3e150778cd550e7f91956aac2
18 files changed, 643 insertions, 790 deletions
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 133549b..7ce96c0 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -45,8 +45,7 @@ import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; -import android.view.InputHandler; -import android.view.InputQueue; +import android.view.InputEventReceiver; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; @@ -228,24 +227,29 @@ public abstract class WallpaperService extends Service { } }; - - final InputHandler mInputHandler = new InputHandler() { + + final class WallpaperInputEventReceiver extends InputEventReceiver { + public WallpaperInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleInputEvent(InputEvent event, - InputQueue.FinishedCallback finishedCallback) { + public void onInputEvent(InputEvent event) { boolean handled = false; try { if (event instanceof MotionEvent && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { - dispatchPointer((MotionEvent)event); + MotionEvent dup = MotionEvent.obtainNoHistory((MotionEvent)event); + dispatchPointer(dup); handled = true; } } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } - }; - + } + WallpaperInputEventReceiver mInputEventReceiver; + final BaseIWindow mWindow = new BaseIWindow() { @Override public void resized(int w, int h, Rect coveredInsets, @@ -534,6 +538,8 @@ public abstract class WallpaperService extends Service { } Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event); mCaller.sendMessage(msg); + } else { + event.recycle(); } } @@ -599,8 +605,8 @@ public abstract class WallpaperService extends Service { } mCreated = true; - InputQueue.registerInputChannel(mInputChannel, mInputHandler, - Looper.myQueue()); + mInputEventReceiver = new WallpaperInputEventReceiver( + mInputChannel, Looper.myLooper()); } mSurfaceHolder.mSurfaceLock.lock(); @@ -902,8 +908,9 @@ public abstract class WallpaperService extends Service { if (DEBUG) Log.v(TAG, "Removing window and destroying surface " + mSurfaceHolder.getSurface() + " of: " + this); - if (mInputChannel != null) { - InputQueue.unregisterInputChannel(mInputChannel); + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; } mSession.remove(mWindow); @@ -970,6 +977,8 @@ public abstract class WallpaperService extends Service { public void dispatchPointer(MotionEvent event) { if (mEngine != null) { mEngine.dispatchPointer(event); + } else { + event.recycle(); } } diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index 01ddcc9..c42bbdc 100755 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -19,6 +19,8 @@ package android.view; import android.os.Parcel; import android.os.Parcelable; +import java.util.concurrent.atomic.AtomicInteger; + /** * Common base class for input events. */ @@ -27,8 +29,21 @@ public abstract class InputEvent implements Parcelable { protected static final int PARCEL_TOKEN_MOTION_EVENT = 1; /** @hide */ protected static final int PARCEL_TOKEN_KEY_EVENT = 2; - + + // Next sequence number. + private static final AtomicInteger mNextSeq = new AtomicInteger(); + + /** @hide */ + protected int mSeq; + + /** @hide */ + protected boolean mRecycled; + + private static final boolean TRACK_RECYCLED_LOCATION = false; + private RuntimeException mRecycledLocation; + /*package*/ InputEvent() { + mSeq = mNextSeq.getAndIncrement(); } /** @@ -82,7 +97,29 @@ public abstract class InputEvent implements Parcelable { * objects are fine. See {@link KeyEvent#recycle()} for details. * @hide */ - public abstract void recycle(); + public void recycle() { + if (TRACK_RECYCLED_LOCATION) { + if (mRecycledLocation != null) { + throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation); + } + mRecycledLocation = new RuntimeException("Last recycled here"); + } else { + if (mRecycled) { + throw new RuntimeException(toString() + " recycled twice!"); + } + mRecycled = true; + } + } + + /** + * Reinitializes the event on reuse (after recycling). + * @hide + */ + protected void prepareForReuse() { + mRecycled = false; + mRecycledLocation = null; + mSeq = mNextSeq.getAndIncrement(); + } /** * Gets a private flag that indicates when the system has detected that this input event @@ -113,6 +150,22 @@ public abstract class InputEvent implements Parcelable { */ public abstract long getEventTimeNano(); + /** + * Gets the unique sequence number of this event. + * Every input event that is created or received by a process has a + * unique sequence number. Moreover, a new sequence number is obtained + * each time an event object is recycled. + * + * Sequence numbers are only guaranteed to be locally unique within a process. + * Sequence numbers are not preserved when events are parceled. + * + * @return The unique sequence number of this event. + * @hide + */ + public int getSequenceNumber() { + return mSeq; + } + public int describeContents() { return 0; } diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java index 9b081b2..fafe416 100644 --- a/core/java/android/view/InputEventConsistencyVerifier.java +++ b/core/java/android/view/InputEventConsistencyVerifier.java @@ -58,7 +58,7 @@ public final class InputEventConsistencyVerifier { // so that the verifier can detect when it has been asked to verify the same event twice. // It does not make sense to examine the contents of the last event since it may have // been recycled. - private InputEvent mLastEvent; + private int mLastEventSeq; private String mLastEventType; private int mLastNestingLevel; @@ -140,7 +140,7 @@ public final class InputEventConsistencyVerifier { * Resets the state of the input event consistency verifier. */ public void reset() { - mLastEvent = null; + mLastEventSeq = -1; mLastNestingLevel = 0; mTrackballDown = false; mTrackballUnhandled = false; @@ -573,17 +573,18 @@ public final class InputEventConsistencyVerifier { private boolean startEvent(InputEvent event, int nestingLevel, String eventType) { // Ignore the event if we already checked it at a higher nesting level. - if (event == mLastEvent && nestingLevel < mLastNestingLevel + final int seq = event.getSequenceNumber(); + if (seq == mLastEventSeq && nestingLevel < mLastNestingLevel && eventType == mLastEventType) { return false; } if (nestingLevel > 0) { - mLastEvent = event; + mLastEventSeq = seq; mLastEventType = eventType; mLastNestingLevel = nestingLevel; } else { - mLastEvent = null; + mLastEventSeq = -1; mLastEventType = null; mLastNestingLevel = 0; } diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java new file mode 100644 index 0000000..abb5281 --- /dev/null +++ b/core/java/android/view/InputEventReceiver.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import dalvik.system.CloseGuard; + +import android.os.Looper; +import android.os.MessageQueue; +import android.util.Log; + +/** + * Provides a low-level mechanism for an application to receive input events. + * @hide + */ +public abstract class InputEventReceiver { + private static final String TAG = "InputEventReceiver"; + + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private int mReceiverPtr; + + // We keep references to the input channel and message queue objects here so that + // they are not GC'd while the native peer of the receiver is using them. + private InputChannel mInputChannel; + private MessageQueue mMessageQueue; + + // The sequence number of the event that is in progress. + private int mEventSequenceNumberInProgress = -1; + + private static native int nativeInit(InputEventReceiver receiver, + InputChannel inputChannel, MessageQueue messageQueue); + private static native void nativeDispose(int receiverPtr); + private static native void nativeFinishInputEvent(int receiverPtr, boolean handled); + + /** + * Creates an input event receiver bound to the specified input channel. + * + * @param inputChannel The input channel. + * @param looper The looper to use when invoking callbacks. + */ + public InputEventReceiver(InputChannel inputChannel, Looper looper) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null"); + } + if (looper == null) { + throw new IllegalArgumentException("looper must not be null"); + } + + mInputChannel = inputChannel; + mMessageQueue = looper.getQueue(); + mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue); + + mCloseGuard.open("dispose"); + } + + @Override + protected void finalize() throws Throwable { + try { + dispose(); + } finally { + super.finalize(); + } + } + + /** + * Disposes the receiver. + */ + public void dispose() { + if (mCloseGuard != null) { + mCloseGuard.close(); + } + if (mReceiverPtr != 0) { + nativeDispose(mReceiverPtr); + mReceiverPtr = 0; + } + mInputChannel = null; + mMessageQueue = null; + } + + /** + * Called when an input event is received. + * The recipient should process the input event and then call {@link #finishInputEvent} + * to indicate whether the event was handled. No new input events will be received + * until {@link #finishInputEvent} is called. + * + * @param event The input event that was received. + */ + public void onInputEvent(InputEvent event) { + finishInputEvent(event, false); + } + + /** + * Finishes an input event and indicates whether it was handled. + * + * @param event The input event that was finished. + * @param handled True if the event was handled. + */ + public void finishInputEvent(InputEvent event, boolean handled) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (mReceiverPtr == 0) { + Log.w(TAG, "Attempted to finish an input event but the input event " + + "receiver has already been disposed."); + } else { + if (event.getSequenceNumber() != mEventSequenceNumberInProgress) { + Log.w(TAG, "Attempted to finish an input event that is not in progress."); + } else { + mEventSequenceNumberInProgress = -1; + nativeFinishInputEvent(mReceiverPtr, handled); + } + } + recycleInputEvent(event); + } + + // Called from native code. + @SuppressWarnings("unused") + private void dispatchInputEvent(InputEvent event) { + mEventSequenceNumberInProgress = event.getSequenceNumber(); + onInputEvent(event); + } + + private static void recycleInputEvent(InputEvent event) { + if (event instanceof MotionEvent) { + // Event though key events are also recyclable, we only recycle motion events. + // Historically, key events were not recyclable and applications expect + // them to be immutable. We only ever recycle key events behind the + // scenes where an application never sees them (so, not here). + event.recycle(); + } + } + + public static interface Factory { + public InputEventReceiver createInputEventReceiver( + InputChannel inputChannel, Looper looper); + } +} diff --git a/core/java/android/view/InputHandler.java b/core/java/android/view/InputHandler.java deleted file mode 100644 index 192a427..0000000 --- a/core/java/android/view/InputHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -/** - * Handles input messages that arrive on an input channel. - * @hide - */ -public class InputHandler { - /** - * Handle an input event. - * It is the responsibility of the callee to ensure that the finished callback is - * eventually invoked when the event processing is finished and the input system - * can send the next event. - * @param event The input event. - * @param finishedCallback The callback to invoke when event processing is finished. - */ - public void handleInputEvent(InputEvent event, InputQueue.FinishedCallback finishedCallback) { - finishedCallback.finished(false); - } -} diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java index 1206518..909a3b2 100644 --- a/core/java/android/view/InputQueue.java +++ b/core/java/android/view/InputQueue.java @@ -16,18 +16,11 @@ package android.view; -import android.os.MessageQueue; -import android.util.Slog; - /** * An input queue provides a mechanism for an application to receive incoming * input events. Currently only usable from native code. */ public final class InputQueue { - private static final String TAG = "InputQueue"; - - private static final boolean DEBUG = false; - /** * Interface to receive notification of when an InputQueue is associated * and dissociated with a thread. @@ -48,13 +41,6 @@ public final class InputQueue { final InputChannel mChannel; - private static final Object sLock = new Object(); - - private static native void nativeRegisterInputChannel(InputChannel inputChannel, - InputHandler inputHandler, MessageQueue messageQueue); - private static native void nativeUnregisterInputChannel(InputChannel inputChannel); - private static native void nativeFinished(long finishedToken, boolean handled); - /** @hide */ public InputQueue(InputChannel channel) { mChannel = channel; @@ -64,114 +50,4 @@ public final class InputQueue { public InputChannel getInputChannel() { return mChannel; } - - /** - * Registers an input channel and handler. - * @param inputChannel The input channel to register. - * @param inputHandler The input handler to input events send to the target. - * @param messageQueue The message queue on whose thread the handler should be invoked. - * @hide - */ - public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler, - MessageQueue messageQueue) { - if (inputChannel == null) { - throw new IllegalArgumentException("inputChannel must not be null"); - } - if (inputHandler == null) { - throw new IllegalArgumentException("inputHandler must not be null"); - } - if (messageQueue == null) { - throw new IllegalArgumentException("messageQueue must not be null"); - } - - synchronized (sLock) { - if (DEBUG) { - Slog.d(TAG, "Registering input channel '" + inputChannel + "'"); - } - - nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue); - } - } - - /** - * Unregisters an input channel. - * Does nothing if the channel is not currently registered. - * @param inputChannel The input channel to unregister. - * @hide - */ - public static void unregisterInputChannel(InputChannel inputChannel) { - if (inputChannel == null) { - throw new IllegalArgumentException("inputChannel must not be null"); - } - - synchronized (sLock) { - if (DEBUG) { - Slog.d(TAG, "Unregistering input channel '" + inputChannel + "'"); - } - - nativeUnregisterInputChannel(inputChannel); - } - } - - @SuppressWarnings("unused") - private static void dispatchInputEvent(InputHandler inputHandler, - InputEvent event, long finishedToken) { - FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken); - inputHandler.handleInputEvent(event, finishedCallback); - } - - /** - * A callback that must be invoked to when finished processing an event. - * @hide - */ - public static final class FinishedCallback { - private static final boolean DEBUG_RECYCLING = false; - - private static final int RECYCLE_MAX_COUNT = 4; - - private static FinishedCallback sRecycleHead; - private static int sRecycleCount; - - private FinishedCallback mRecycleNext; - private long mFinishedToken; - - private FinishedCallback() { - } - - public static FinishedCallback obtain(long finishedToken) { - synchronized (sLock) { - FinishedCallback callback = sRecycleHead; - if (callback != null) { - sRecycleHead = callback.mRecycleNext; - sRecycleCount -= 1; - callback.mRecycleNext = null; - } else { - callback = new FinishedCallback(); - } - callback.mFinishedToken = finishedToken; - return callback; - } - } - - public void finished(boolean handled) { - synchronized (sLock) { - if (mFinishedToken == -1) { - throw new IllegalStateException("Event finished callback already invoked."); - } - - nativeFinished(mFinishedToken, handled); - mFinishedToken = -1; - - if (sRecycleCount < RECYCLE_MAX_COUNT) { - mRecycleNext = sRecycleHead; - sRecycleHead = this; - sRecycleCount += 1; - - if (DEBUG_RECYCLING) { - Slog.d(TAG, "Recycled finished callbacks: " + sRecycleCount); - } - } - } - } - } } diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index f53e42c..9a46aab 100755 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -1225,7 +1225,6 @@ public class KeyEvent extends InputEvent implements Parcelable { private static KeyEvent gRecyclerTop; private KeyEvent mNext; - private boolean mRecycled; private int mDeviceId; private int mSource; @@ -1535,8 +1534,8 @@ public class KeyEvent extends InputEvent implements Parcelable { gRecyclerTop = ev.mNext; gRecyclerUsed -= 1; } - ev.mRecycled = false; ev.mNext = null; + ev.prepareForReuse(); return ev; } @@ -1598,10 +1597,7 @@ public class KeyEvent extends InputEvent implements Parcelable { * @hide */ public final void recycle() { - if (mRecycled) { - throw new RuntimeException(toString() + " recycled twice!"); - } - mRecycled = true; + super.recycle(); mCharacters = null; synchronized (gRecyclerLock) { diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 8e0ab1a..e49193e 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -167,7 +167,6 @@ import android.util.SparseArray; */ public final class MotionEvent extends InputEvent implements Parcelable { private static final long NS_PER_MS = 1000000; - private static final boolean TRACK_RECYCLED_LOCATION = false; /** * An invalid pointer id. @@ -1315,8 +1314,6 @@ public final class MotionEvent extends InputEvent implements Parcelable { private int mNativePtr; private MotionEvent mNext; - private RuntimeException mRecycledLocation; - private boolean mRecycled; private static native int nativeInitialize(int nativePtr, int deviceId, int source, int action, int flags, int edgeFlags, @@ -1397,9 +1394,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { gRecyclerTop = ev.mNext; gRecyclerUsed -= 1; } - ev.mRecycledLocation = null; - ev.mRecycled = false; ev.mNext = null; + ev.prepareForReuse(); return ev; } @@ -1647,19 +1643,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * this function you must not ever touch the event again. */ public final void recycle() { - // Ensure recycle is only called once! - if (TRACK_RECYCLED_LOCATION) { - if (mRecycledLocation != null) { - throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation); - } - mRecycledLocation = new RuntimeException("Last recycled here"); - //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation); - } else { - if (mRecycled) { - throw new RuntimeException(toString() + " recycled twice!"); - } - mRecycled = true; - } + super.recycle(); synchronized (gRecyclerLock) { if (gRecyclerUsed < MAX_RECYCLED) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0e65334..f23c312 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -221,7 +221,6 @@ public final class ViewRootImpl extends Handler implements ViewParent, private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10; private QueuedInputEvent mQueuedInputEventPool; private int mQueuedInputEventPoolSize; - private int mQueuedInputEventNextSeq; // Input event queue. QueuedInputEvent mFirstPendingInputEvent; @@ -559,8 +558,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, mInputQueue = new InputQueue(mInputChannel); mInputQueueCallback.onInputQueueCreated(mInputQueue); } else { - InputQueue.registerInputChannel(mInputChannel, mInputHandler, - Looper.myQueue()); + mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, + Looper.myLooper()); } } @@ -2283,8 +2282,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, mInputQueueCallback.onInputQueueDestroyed(mInputQueue); mInputQueueCallback = null; mInputQueue = null; - } else if (mInputChannel != null) { - InputQueue.unregisterInputChannel(mInputChannel); + } else if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; } try { sWindowSession.remove(mWindow); @@ -3199,9 +3199,10 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (mLastWasImTarget) { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { + final int seq = event.getSequenceNumber(); if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq=" - + q.mSeq + " event=" + event); - imm.dispatchKeyEvent(mView.getContext(), q.mSeq, event, mInputMethodCallback); + + seq + " event=" + event); + imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback); return; } } @@ -3213,7 +3214,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, void handleImeFinishedEvent(int seq, boolean handled) { final QueuedInputEvent q = mCurrentInputEvent; - if (q != null && q.mSeq == seq) { + if (q != null && q.mEvent.getSequenceNumber() == seq) { final KeyEvent event = (KeyEvent)q.mEvent; if (DEBUG_IMF) { Log.v(TAG, "IME finished event: seq=" + seq @@ -3715,9 +3716,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, public QueuedInputEvent mNext; public InputEvent mEvent; - public InputQueue.FinishedCallback mFinishedCallback; + public InputEventReceiver mReceiver; public int mFlags; - public int mSeq; // Used for latency calculations. public long mReceiveTimeNanos; @@ -3726,7 +3726,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, } private QueuedInputEvent obtainQueuedInputEvent(InputEvent event, - InputQueue.FinishedCallback finishedCallback, int flags) { + InputEventReceiver receiver, int flags) { QueuedInputEvent q = mQueuedInputEventPool; if (q != null) { mQueuedInputEventPoolSize -= 1; @@ -3737,15 +3737,14 @@ public final class ViewRootImpl extends Handler implements ViewParent, } q.mEvent = event; - q.mFinishedCallback = finishedCallback; + q.mReceiver = receiver; q.mFlags = flags; - q.mSeq = mQueuedInputEventNextSeq++; return q; } private void recycleQueuedInputEvent(QueuedInputEvent q) { q.mEvent = null; - q.mFinishedCallback = null; + q.mReceiver = null; if (mQueuedInputEventPoolSize < MAX_QUEUED_INPUT_EVENT_POOL_SIZE) { mQueuedInputEventPoolSize += 1; @@ -3755,8 +3754,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, } void enqueueInputEvent(InputEvent event, - InputQueue.FinishedCallback finishedCallback, int flags) { - QueuedInputEvent q = obtainQueuedInputEvent(event, finishedCallback, flags); + InputEventReceiver receiver, int flags) { + QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); if (ViewDebug.DEBUG_LATENCY) { q.mReceiveTimeNanos = System.nanoTime(); @@ -3847,11 +3846,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, Log.d(ViewDebug.DEBUG_LATENCY_TAG, msg.toString()); } - if (q.mFinishedCallback != null) { - q.mFinishedCallback.finished(handled); - } - - if (q.mEvent instanceof MotionEvent) { + if (q.mReceiver != null) { + q.mReceiver.finishInputEvent(q.mEvent, handled); + } else if (q.mEvent instanceof MotionEvent) { // Event though key events are also recyclable, we only recycle motion events. // Historically, key events were not recyclable and applications expect // them to be immutable. We only ever recycle key events behind the @@ -3867,12 +3864,17 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - private final InputHandler mInputHandler = new InputHandler() { - public void handleInputEvent(InputEvent event, - InputQueue.FinishedCallback finishedCallback) { - enqueueInputEvent(event, finishedCallback, 0); + final class WindowInputEventReceiver extends InputEventReceiver { + public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); } - }; + + @Override + public void onInputEvent(InputEvent event) { + enqueueInputEvent(event, this, 0); + } + } + WindowInputEventReceiver mInputEventReceiver; public void dispatchKey(KeyEvent event) { enqueueInputEvent(event, null, 0); diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 2e19bf6..7d729c6 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -340,7 +340,8 @@ public interface WindowManagerPolicy { * Add a fake window to the window manager. This window sits * at the top of the other windows and consumes events. */ - public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler, + public FakeWindow addFakeWindow(Looper looper, + InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen); } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 71c5d26..f20fbbb 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -50,7 +50,7 @@ LOCAL_SRC_FILES:= \ android_view_Surface.cpp \ android_view_TextureView.cpp \ android_view_InputChannel.cpp \ - android_view_InputQueue.cpp \ + android_view_InputEventReceiver.cpp \ android_view_KeyEvent.cpp \ android_view_KeyCharacterMap.cpp \ android_view_HardwareRenderer.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 6d1410c..c6447e1 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -168,7 +168,7 @@ extern int register_android_app_backup_FullBackup(JNIEnv *env); extern int register_android_app_ActivityThread(JNIEnv *env); extern int register_android_app_NativeActivity(JNIEnv *env); extern int register_android_view_InputChannel(JNIEnv* env); -extern int register_android_view_InputQueue(JNIEnv* env); +extern int register_android_view_InputEventReceiver(JNIEnv* env); extern int register_android_view_KeyEvent(JNIEnv* env); extern int register_android_view_MotionEvent(JNIEnv* env); extern int register_android_view_PointerIcon(JNIEnv* env); @@ -1192,7 +1192,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_app_ActivityThread), REG_JNI(register_android_app_NativeActivity), REG_JNI(register_android_view_InputChannel), - REG_JNI(register_android_view_InputQueue), + REG_JNI(register_android_view_InputEventReceiver), REG_JNI(register_android_view_KeyEvent), REG_JNI(register_android_view_MotionEvent), REG_JNI(register_android_view_PointerIcon), diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp new file mode 100644 index 0000000..9ae63dd --- /dev/null +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "InputEventReceiver" + +//#define LOG_NDEBUG 0 + +// Log debug messages about the dispatch cycle. +#define DEBUG_DISPATCH_CYCLE 0 + + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <utils/Log.h> +#include <utils/Looper.h> +#include <utils/KeyedVector.h> +#include <utils/threads.h> +#include <ui/InputTransport.h> +#include "android_os_MessageQueue.h" +#include "android_view_InputChannel.h" +#include "android_view_KeyEvent.h" +#include "android_view_MotionEvent.h" + +namespace android { + +static struct { + jclass clazz; + + jmethodID dispatchInputEvent; +} gInputEventReceiverClassInfo; + + +class NativeInputEventReceiver : public RefBase { +public: + NativeInputEventReceiver(JNIEnv* env, + jobject receiverObj, const sp<InputChannel>& inputChannel, + const sp<Looper>& looper); + + status_t initialize(); + status_t finishInputEvent(bool handled); + static int handleReceiveCallback(int receiveFd, int events, void* data); + +protected: + virtual ~NativeInputEventReceiver(); + +private: + jobject mReceiverObjGlobal; + InputConsumer mInputConsumer; + sp<Looper> mLooper; + bool mEventInProgress; + PreallocatedInputEventFactory mInputEventFactory; + + const char* getInputChannelName() { + return mInputConsumer.getChannel()->getName().string(); + } +}; + + +NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, + jobject receiverObj, const sp<InputChannel>& inputChannel, const sp<Looper>& looper) : + mReceiverObjGlobal(env->NewGlobalRef(receiverObj)), + mInputConsumer(inputChannel), mLooper(looper), mEventInProgress(false) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName()); +#endif +} + +NativeInputEventReceiver::~NativeInputEventReceiver() { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName()); +#endif + + mLooper->removeFd(mInputConsumer.getChannel()->getReceivePipeFd()); + if (mEventInProgress) { + mInputConsumer.sendFinishedSignal(false); // ignoring result + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mReceiverObjGlobal); +} + +status_t NativeInputEventReceiver::initialize() { + status_t result = mInputConsumer.initialize(); + if (result) { + LOGW("Failed to initialize input consumer for input channel '%s', status=%d", + getInputChannelName(), result); + return result; + } + + int32_t receiveFd = mInputConsumer.getChannel()->getReceivePipeFd(); + mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); + return OK; +} + +status_t NativeInputEventReceiver::finishInputEvent(bool handled) { + if (mEventInProgress) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Finished input event.", getInputChannelName()); +#endif + mEventInProgress = false; + + status_t status = mInputConsumer.sendFinishedSignal(handled); + if (status) { + LOGW("Failed to send finished signal on channel '%s'. status=%d", + getInputChannelName(), status); + } + return status; + } else { + LOGW("Ignoring attempt to finish input event while no event is in progress."); + return OK; + } +} + +int NativeInputEventReceiver::handleReceiveCallback(int receiveFd, int events, void* data) { + sp<NativeInputEventReceiver> r = static_cast<NativeInputEventReceiver*>(data); + + if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { + LOGE("channel '%s' ~ Publisher closed input channel or an error occurred. " + "events=0x%x", r->getInputChannelName(), events); + return 0; // remove the callback + } + + if (!(events & ALOOPER_EVENT_INPUT)) { + LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " + "events=0x%x", r->getInputChannelName(), events); + return 1; + } + + status_t status = r->mInputConsumer.receiveDispatchSignal(); + if (status) { + LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", + r->getInputChannelName(), status); + return 0; // remove the callback + } + + if (r->mEventInProgress) { + LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.", + r->getInputChannelName()); + return 1; + } + + InputEvent* inputEvent; + status = r->mInputConsumer.consume(&r->mInputEventFactory, &inputEvent); + if (status) { + LOGW("channel '%s' ~ Failed to consume input event. status=%d", + r->getInputChannelName(), status); + r->mInputConsumer.sendFinishedSignal(false); + return 1; + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jobject inputEventObj; + switch (inputEvent->getType()) { + case AINPUT_EVENT_TYPE_KEY: +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Received key event.", + r->getInputChannelName()); +#endif + inputEventObj = android_view_KeyEvent_fromNative(env, + static_cast<KeyEvent*>(inputEvent)); + break; + + case AINPUT_EVENT_TYPE_MOTION: +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Received motion event.", + r->getInputChannelName()); +#endif + inputEventObj = android_view_MotionEvent_obtainAsCopy(env, + static_cast<MotionEvent*>(inputEvent)); + break; + + default: + assert(false); // InputConsumer should prevent this from ever happening + inputEventObj = NULL; + } + + if (!inputEventObj) { + LOGW("channel '%s' ~ Failed to obtain event object.", + r->getInputChannelName()); + r->mInputConsumer.sendFinishedSignal(false); + return 1; + } + + r->mEventInProgress = true; + +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Invoking input handler.", r->getInputChannelName()); +#endif + env->CallVoidMethod(r->mReceiverObjGlobal, + gInputEventReceiverClassInfo.dispatchInputEvent, inputEventObj); +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Returned from input handler.", r->getInputChannelName()); +#endif + + if (env->ExceptionCheck()) { + LOGE("channel '%s' ~ An exception occurred while dispatching an event.", + r->getInputChannelName()); + LOGE_EX(env); + env->ExceptionClear(); + + if (r->mEventInProgress) { + r->mInputConsumer.sendFinishedSignal(false); + r->mEventInProgress = false; + } + } + + env->DeleteLocalRef(inputEventObj); + return 1; +} + + +static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj, + jobject inputChannelObj, jobject messageQueueObj) { + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + jniThrowRuntimeException(env, "InputChannel is not initialized."); + return 0; + } + + sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj); + if (looper == NULL) { + jniThrowRuntimeException(env, "MessageQueue is not initialized."); + return 0; + } + + sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env, + receiverObj, inputChannel, looper); + status_t status = receiver->initialize(); + if (status) { + String8 message; + message.appendFormat("Failed to initialize input event receiver. status=%d", status); + jniThrowRuntimeException(env, message.string()); + return 0; + } + + receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object + return reinterpret_cast<jint>(receiver.get()); +} + +static void nativeDispose(JNIEnv* env, jclass clazz, jint receiverPtr) { + sp<NativeInputEventReceiver> receiver = + reinterpret_cast<NativeInputEventReceiver*>(receiverPtr); + receiver->decStrong(gInputEventReceiverClassInfo.clazz); // drop reference held by the object +} + +static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr, jboolean handled) { + sp<NativeInputEventReceiver> receiver = + reinterpret_cast<NativeInputEventReceiver*>(receiverPtr); + status_t status = receiver->finishInputEvent(handled); + if (status) { + String8 message; + message.appendFormat("Failed to finish input event. status=%d", status); + jniThrowRuntimeException(env, message.string()); + } +} + + +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "nativeInit", + "(Landroid/view/InputEventReceiver;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I", + (void*)nativeInit }, + { "nativeDispose", + "(I)V", + (void*)nativeDispose }, + { "nativeFinishInputEvent", "(IZ)V", + (void*)nativeFinishInputEvent } +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method " methodName); + +int register_android_view_InputEventReceiver(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/InputEventReceiver", + gMethods, NELEM(gMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputEventReceiverClassInfo.clazz, "android/view/InputEventReceiver"); + + GET_METHOD_ID(gInputEventReceiverClassInfo.dispatchInputEvent, + gInputEventReceiverClassInfo.clazz, + "dispatchInputEvent", "(Landroid/view/InputEvent;)V"); + return 0; +} + +} // namespace android diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp deleted file mode 100644 index 549f575..0000000 --- a/core/jni/android_view_InputQueue.cpp +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "InputQueue-JNI" - -//#define LOG_NDEBUG 0 - -// Log debug messages about the dispatch cycle. -#define DEBUG_DISPATCH_CYCLE 0 - -// Log debug messages about registrations. -#define DEBUG_REGISTRATION 0 - - -#include "JNIHelp.h" - -#include <android_runtime/AndroidRuntime.h> -#include <utils/Log.h> -#include <utils/Looper.h> -#include <utils/KeyedVector.h> -#include <utils/threads.h> -#include <ui/InputTransport.h> -#include "android_os_MessageQueue.h" -#include "android_view_InputChannel.h" -#include "android_view_KeyEvent.h" -#include "android_view_MotionEvent.h" - -namespace android { - -// ---------------------------------------------------------------------------- - -static struct { - jclass clazz; - - jmethodID dispatchInputEvent; -} gInputQueueClassInfo; - -// ---------------------------------------------------------------------------- - -class NativeInputQueue { -public: - NativeInputQueue(); - ~NativeInputQueue(); - - status_t registerInputChannel(JNIEnv* env, jobject inputChannelObj, - jobject inputHandlerObj, jobject messageQueueObj); - - status_t unregisterInputChannel(JNIEnv* env, jobject inputChannelObj); - - status_t finished(JNIEnv* env, jlong finishedToken, bool handled, bool ignoreSpuriousFinish); - -private: - class Connection : public RefBase { - protected: - virtual ~Connection(); - - public: - enum Status { - // Everything is peachy. - STATUS_NORMAL, - // The input channel has been unregistered. - STATUS_ZOMBIE - }; - - Connection(uint16_t id, - const sp<InputChannel>& inputChannel, const sp<Looper>& looper); - - inline const char* getInputChannelName() const { return inputChannel->getName().string(); } - - // A unique id for this connection. - uint16_t id; - - Status status; - - sp<InputChannel> inputChannel; - InputConsumer inputConsumer; - sp<Looper> looper; - jobject inputHandlerObjGlobal; - PreallocatedInputEventFactory inputEventFactory; - - // The sequence number of the current event being dispatched. - // This is used as part of the finished token as a way to determine whether the finished - // token is still valid before sending a finished signal back to the publisher. - uint16_t messageSeqNum; - - // True if a message has been received from the publisher but not yet finished. - bool messageInProgress; - }; - - Mutex mLock; - uint16_t mNextConnectionId; - KeyedVector<int32_t, sp<Connection> > mConnectionsByReceiveFd; - - ssize_t getConnectionIndex(const sp<InputChannel>& inputChannel); - - static void handleInputChannelDisposed(JNIEnv* env, - jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data); - - static int handleReceiveCallback(int receiveFd, int events, void* data); - - static jlong generateFinishedToken(int32_t receiveFd, - uint16_t connectionId, uint16_t messageSeqNum); - - static void parseFinishedToken(jlong finishedToken, - int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex); -}; - -// ---------------------------------------------------------------------------- - -NativeInputQueue::NativeInputQueue() : - mNextConnectionId(0) { -} - -NativeInputQueue::~NativeInputQueue() { -} - -status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj, - jobject inputHandlerObj, jobject messageQueueObj) { - sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, - inputChannelObj); - if (inputChannel == NULL) { - LOGW("Input channel is not initialized."); - return BAD_VALUE; - } - -#if DEBUG_REGISTRATION - LOGD("channel '%s' - Registered", inputChannel->getName().string()); -#endif - - sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj); - - { // acquire lock - AutoMutex _l(mLock); - - if (getConnectionIndex(inputChannel) >= 0) { - LOGW("Attempted to register already registered input channel '%s'", - inputChannel->getName().string()); - return BAD_VALUE; - } - - uint16_t connectionId = mNextConnectionId++; - sp<Connection> connection = new Connection(connectionId, inputChannel, looper); - status_t result = connection->inputConsumer.initialize(); - if (result) { - LOGW("Failed to initialize input consumer for input channel '%s', status=%d", - inputChannel->getName().string(), result); - return result; - } - - connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj); - - int32_t receiveFd = inputChannel->getReceivePipeFd(); - mConnectionsByReceiveFd.add(receiveFd, connection); - - looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); - } // release lock - - android_view_InputChannel_setDisposeCallback(env, inputChannelObj, - handleInputChannelDisposed, this); - return OK; -} - -status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) { - sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, - inputChannelObj); - if (inputChannel == NULL) { - LOGW("Input channel is not initialized."); - return BAD_VALUE; - } - -#if DEBUG_REGISTRATION - LOGD("channel '%s' - Unregistered", inputChannel->getName().string()); -#endif - - { // acquire lock - AutoMutex _l(mLock); - - ssize_t connectionIndex = getConnectionIndex(inputChannel); - if (connectionIndex < 0) { - LOGW("Attempted to unregister already unregistered input channel '%s'", - inputChannel->getName().string()); - return BAD_VALUE; - } - - sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); - mConnectionsByReceiveFd.removeItemsAt(connectionIndex); - - connection->status = Connection::STATUS_ZOMBIE; - - connection->looper->removeFd(inputChannel->getReceivePipeFd()); - - env->DeleteGlobalRef(connection->inputHandlerObjGlobal); - connection->inputHandlerObjGlobal = NULL; - - if (connection->messageInProgress) { - LOGI("Sending finished signal for input channel '%s' since it is being unregistered " - "while an input message is still in progress.", - connection->getInputChannelName()); - connection->messageInProgress = false; - connection->inputConsumer.sendFinishedSignal(false); // ignoring result - } - } // release lock - - android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL); - return OK; -} - -ssize_t NativeInputQueue::getConnectionIndex(const sp<InputChannel>& inputChannel) { - ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd()); - if (connectionIndex >= 0) { - sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); - if (connection->inputChannel.get() == inputChannel.get()) { - return connectionIndex; - } - } - - return -1; -} - -status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, - bool handled, bool ignoreSpuriousFinish) { - int32_t receiveFd; - uint16_t connectionId; - uint16_t messageSeqNum; - parseFinishedToken(finishedToken, &receiveFd, &connectionId, &messageSeqNum); - - { // acquire lock - AutoMutex _l(mLock); - - ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); - if (connectionIndex < 0) { - if (! ignoreSpuriousFinish) { - LOGI("Ignoring finish signal on channel that is no longer registered."); - } - return DEAD_OBJECT; - } - - sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); - if (connectionId != connection->id) { - if (! ignoreSpuriousFinish) { - LOGI("Ignoring finish signal on channel that is no longer registered."); - } - return DEAD_OBJECT; - } - - if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) { - if (! ignoreSpuriousFinish) { - LOGW("Attempted to finish input twice on channel '%s'. " - "finished messageSeqNum=%d, current messageSeqNum=%d, messageInProgress=%d", - connection->getInputChannelName(), - messageSeqNum, connection->messageSeqNum, connection->messageInProgress); - } - return INVALID_OPERATION; - } - - connection->messageInProgress = false; - - status_t status = connection->inputConsumer.sendFinishedSignal(handled); - if (status) { - LOGW("Failed to send finished signal on channel '%s'. status=%d", - connection->getInputChannelName(), status); - return status; - } - -#if DEBUG_DISPATCH_CYCLE - LOGD("channel '%s' ~ Finished event.", - connection->getInputChannelName()); -#endif - } // release lock - - return OK; -} - -void NativeInputQueue::handleInputChannelDisposed(JNIEnv* env, - jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) { - LOGW("Input channel object '%s' was disposed without first being unregistered with " - "the input queue!", inputChannel->getName().string()); - - NativeInputQueue* q = static_cast<NativeInputQueue*>(data); - q->unregisterInputChannel(env, inputChannelObj); -} - -int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) { - NativeInputQueue* q = static_cast<NativeInputQueue*>(data); - JNIEnv* env = AndroidRuntime::getJNIEnv(); - - sp<Connection> connection; - InputEvent* inputEvent; - jobject inputHandlerObjLocal; - jlong finishedToken; - { // acquire lock - AutoMutex _l(q->mLock); - - ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd); - if (connectionIndex < 0) { - LOGE("Received spurious receive callback for unknown input channel. " - "fd=%d, events=0x%x", receiveFd, events); - return 0; // remove the callback - } - - connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex); - if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { - LOGE("channel '%s' ~ Publisher closed input channel or an error occurred. " - "events=0x%x", connection->getInputChannelName(), events); - return 0; // remove the callback - } - - if (! (events & ALOOPER_EVENT_INPUT)) { - LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " - "events=0x%x", connection->getInputChannelName(), events); - return 1; - } - - status_t status = connection->inputConsumer.receiveDispatchSignal(); - if (status) { - LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", - connection->getInputChannelName(), status); - return 0; // remove the callback - } - - if (connection->messageInProgress) { - LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.", - connection->getInputChannelName()); - return 1; - } - - status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent); - if (status) { - LOGW("channel '%s' ~ Failed to consume input event. status=%d", - connection->getInputChannelName(), status); - connection->inputConsumer.sendFinishedSignal(false); - return 1; - } - - connection->messageInProgress = true; - connection->messageSeqNum += 1; - - finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum); - - inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal); - } // release lock - - // Invoke the handler outside of the lock. - // - // Note: inputEvent is stored in a field of the connection object which could potentially - // become disposed due to the input channel being unregistered concurrently. - // For this reason, we explicitly keep the connection object alive by holding - // a strong pointer to it within this scope. We also grabbed a local reference to - // the input handler object itself for the same reason. - - int32_t inputEventType = inputEvent->getType(); - - jobject inputEventObj; - switch (inputEventType) { - case AINPUT_EVENT_TYPE_KEY: -#if DEBUG_DISPATCH_CYCLE - LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName()); -#endif - inputEventObj = android_view_KeyEvent_fromNative(env, - static_cast<KeyEvent*>(inputEvent)); - break; - - case AINPUT_EVENT_TYPE_MOTION: -#if DEBUG_DISPATCH_CYCLE - LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName()); -#endif - inputEventObj = android_view_MotionEvent_obtainAsCopy(env, - static_cast<MotionEvent*>(inputEvent)); - break; - - default: - assert(false); // InputConsumer should prevent this from ever happening - inputEventObj = NULL; - } - - if (! inputEventObj) { - LOGW("channel '%s' ~ Failed to obtain DVM event object.", - connection->getInputChannelName()); - env->DeleteLocalRef(inputHandlerObjLocal); - q->finished(env, finishedToken, false, false); - return 1; - } - -#if DEBUG_DISPATCH_CYCLE - LOGD("Invoking input handler."); -#endif - env->CallStaticVoidMethod(gInputQueueClassInfo.clazz, - gInputQueueClassInfo.dispatchInputEvent, inputHandlerObjLocal, inputEventObj, - jlong(finishedToken)); -#if DEBUG_DISPATCH_CYCLE - LOGD("Returned from input handler."); -#endif - - if (env->ExceptionCheck()) { - LOGE("An exception occurred while invoking the input handler for an event."); - LOGE_EX(env); - env->ExceptionClear(); - - q->finished(env, finishedToken, false, true /*ignoreSpuriousFinish*/); - } - - env->DeleteLocalRef(inputEventObj); - env->DeleteLocalRef(inputHandlerObjLocal); - return 1; -} - -jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId, - uint16_t messageSeqNum) { - return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum); -} - -void NativeInputQueue::parseFinishedToken(jlong finishedToken, - int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex) { - *outReceiveFd = int32_t(finishedToken >> 32); - *outConnectionId = uint16_t(finishedToken >> 16); - *outMessageIndex = uint16_t(finishedToken); -} - -// ---------------------------------------------------------------------------- - -NativeInputQueue::Connection::Connection(uint16_t id, - const sp<InputChannel>& inputChannel, const sp<Looper>& looper) : - id(id), status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel), - looper(looper), inputHandlerObjGlobal(NULL), - messageSeqNum(0), messageInProgress(false) { -} - -NativeInputQueue::Connection::~Connection() { -} - -// ---------------------------------------------------------------------------- - -static NativeInputQueue gNativeInputQueue; - -static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, - jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) { - status_t status = gNativeInputQueue.registerInputChannel( - env, inputChannelObj, inputHandlerObj, messageQueueObj); - - if (status) { - String8 message; - message.appendFormat("Failed to register input channel. status=%d", status); - jniThrowRuntimeException(env, message.string()); - } -} - -static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, - jobject inputChannelObj) { - status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj); - - if (status) { - String8 message; - message.appendFormat("Failed to unregister input channel. status=%d", status); - jniThrowRuntimeException(env, message.string()); - } -} - -static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz, - jlong finishedToken, bool handled) { - status_t status = gNativeInputQueue.finished( - env, finishedToken, handled, false /*ignoreSpuriousFinish*/); - - // We ignore the case where an event could not be finished because the input channel - // was no longer registered (DEAD_OBJECT) since it is a common race that can occur - // during application shutdown. The input dispatcher recovers gracefully anyways. - if (status != OK && status != DEAD_OBJECT) { - String8 message; - message.appendFormat("Failed to finish input event. status=%d", status); - jniThrowRuntimeException(env, message.string()); - } -} - -// ---------------------------------------------------------------------------- - -static JNINativeMethod gInputQueueMethods[] = { - /* name, signature, funcPtr */ - { "nativeRegisterInputChannel", - "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V", - (void*)android_view_InputQueue_nativeRegisterInputChannel }, - { "nativeUnregisterInputChannel", - "(Landroid/view/InputChannel;)V", - (void*)android_view_InputQueue_nativeUnregisterInputChannel }, - { "nativeFinished", "(JZ)V", - (void*)android_view_InputQueue_nativeFinished } -}; - -#define FIND_CLASS(var, className) \ - var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); - -#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \ - var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find static method " methodName); - -int register_android_view_InputQueue(JNIEnv* env) { - int res = jniRegisterNativeMethods(env, "android/view/InputQueue", - gInputQueueMethods, NELEM(gInputQueueMethods)); - LOG_FATAL_IF(res < 0, "Unable to register native methods."); - - FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue"); - - GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchInputEvent, gInputQueueClassInfo.clazz, - "dispatchInputEvent", - "(Landroid/view/InputHandler;Landroid/view/InputEvent;J)V"); - return 0; -} - -} // namespace android diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 08cef01..e6b86fc 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -45,6 +45,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.LocalPowerManager; +import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; @@ -75,8 +76,7 @@ import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; -import android.view.InputQueue; -import android.view.InputHandler; +import android.view.InputEventReceiver; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -345,10 +345,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowState mFocusedWindow; IApplicationToken mFocusedApp; - private final InputHandler mPointerLocationInputHandler = new InputHandler() { + final class PointerLocationInputEventReceiver extends InputEventReceiver { + public PointerLocationInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleInputEvent(InputEvent event, - InputQueue.FinishedCallback finishedCallback) { + public void onInputEvent(InputEvent event) { boolean handled = false; try { if (event instanceof MotionEvent @@ -362,11 +365,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } - }; - + } + PointerLocationInputEventReceiver mPointerLocationInputEventReceiver; + // The current size of the screen; really; (ir)regardless of whether the status // bar can be hidden or not int mUnrestrictedScreenLeft, mUnrestrictedScreenTop; @@ -1003,9 +1007,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mPointerLocationInputChannel == null) { try { mPointerLocationInputChannel = - mWindowManager.monitorInput("PointerLocationView"); - InputQueue.registerInputChannel(mPointerLocationInputChannel, - mPointerLocationInputHandler, mHandler.getLooper().getQueue()); + mWindowManager.monitorInput("PointerLocationView"); + mPointerLocationInputEventReceiver = + new PointerLocationInputEventReceiver( + mPointerLocationInputChannel, mHandler.getLooper()); } catch (RemoteException ex) { Slog.e(TAG, "Could not set up input monitoring channel for PointerLocation.", ex); @@ -1013,8 +1018,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } if (removeView != null) { + if (mPointerLocationInputEventReceiver != null) { + mPointerLocationInputEventReceiver.dispose(); + mPointerLocationInputEventReceiver = null; + } if (mPointerLocationInputChannel != null) { - InputQueue.unregisterInputChannel(mPointerLocationInputChannel); mPointerLocationInputChannel.dispose(); mPointerLocationInputChannel = null; } @@ -1839,10 +1847,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { * to determine when the nav bar should be shown and prevent applications from * receiving those touches. */ - final InputHandler mHideNavInputHandler = new InputHandler() { + final class HideNavInputEventReceiver extends InputEventReceiver { + public HideNavInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleInputEvent(InputEvent event, - InputQueue.FinishedCallback finishedCallback) { + public void onInputEvent(InputEvent event) { boolean handled = false; try { if (event instanceof MotionEvent @@ -1885,9 +1896,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } + } + final InputEventReceiver.Factory mHideNavInputEventReceiverFactory = + new InputEventReceiver.Factory() { + @Override + public InputEventReceiver createInputEventReceiver( + InputChannel inputChannel, Looper looper) { + return new HideNavInputEventReceiver(inputChannel, looper); + } }; @Override @@ -1951,7 +1970,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } else if (mHideNavFakeWindow == null) { mHideNavFakeWindow = mWindowManagerFuncs.addFakeWindow( - mHandler.getLooper(), mHideNavInputHandler, + mHandler.getLooper(), mHideNavInputEventReceiverFactory, "hidden nav", WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER, 0, false, false, true); } diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java index 73cd64e..a19035a 100644 --- a/services/java/com/android/server/wm/DragState.java +++ b/services/java/com/android/server/wm/DragState.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import com.android.server.wm.WindowManagerService.DragInputEventReceiver; import com.android.server.wm.WindowManagerService.H; import android.content.ClipData; @@ -28,7 +29,6 @@ import android.os.RemoteException; import android.util.Slog; import android.view.DragEvent; import android.view.InputChannel; -import android.view.InputQueue; import android.view.Surface; import android.view.View; import android.view.WindowManager; @@ -50,6 +50,7 @@ class DragState { float mCurrentX, mCurrentY; float mThumbOffsetX, mThumbOffsetY; InputChannel mServerChannel, mClientChannel; + DragInputEventReceiver mInputEventReceiver; InputApplicationHandle mDragApplicationHandle; InputWindowHandle mDragWindowHandle; WindowState mTargetWindow; @@ -90,8 +91,8 @@ class DragState { mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel, null); - InputQueue.registerInputChannel(mClientChannel, mService.mDragInputHandler, - mService.mH.getLooper().getQueue()); + mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel, + mService.mH.getLooper()); mDragApplicationHandle = new InputApplicationHandle(null); mDragApplicationHandle.name = "drag"; @@ -139,7 +140,8 @@ class DragState { Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel"); } else { mService.mInputManager.unregisterInputChannel(mServerChannel); - InputQueue.unregisterInputChannel(mClientChannel); + mInputEventReceiver.dispose(); + mInputEventReceiver = null; mClientChannel.dispose(); mServerChannel.dispose(); mClientChannel = null; diff --git a/services/java/com/android/server/wm/FakeWindowImpl.java b/services/java/com/android/server/wm/FakeWindowImpl.java index 0e72f7d..121ce18 100644 --- a/services/java/com/android/server/wm/FakeWindowImpl.java +++ b/services/java/com/android/server/wm/FakeWindowImpl.java @@ -20,7 +20,7 @@ import android.os.Looper; import android.os.Process; import android.util.Slog; import android.view.InputChannel; -import android.view.InputHandler; +import android.view.InputEventReceiver; import android.view.InputQueue; import android.view.WindowManagerPolicy; @@ -29,11 +29,13 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { final InputChannel mServerChannel, mClientChannel; final InputApplicationHandle mApplicationHandle; final InputWindowHandle mWindowHandle; + final InputEventReceiver mInputEventReceiver; final int mWindowLayer; boolean mTouchFullscreen; - public FakeWindowImpl(WindowManagerService service, Looper looper, InputHandler inputHandler, + public FakeWindowImpl(WindowManagerService service, + Looper looper, InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { mService = service; @@ -42,7 +44,9 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel, null); - InputQueue.registerInputChannel(mClientChannel, inputHandler, looper.getQueue()); + + mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver( + mClientChannel, looper); mApplicationHandle = new InputApplicationHandle(null); mApplicationHandle.name = name; @@ -87,8 +91,8 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { public void dismiss() { synchronized (mService.mWindowMap) { if (mService.removeFakeWindowLocked(this)) { + mInputEventReceiver.dispose(); mService.mInputManager.unregisterInputChannel(mServerChannel); - InputQueue.unregisterInputChannel(mClientChannel); mClientChannel.dispose(); mServerChannel.dispose(); } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index d7dcacb..75ace4f 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -106,8 +106,7 @@ import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; -import android.view.InputHandler; -import android.view.InputQueue; +import android.view.InputEventReceiver; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -570,10 +569,14 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; DragState mDragState = null; - final InputHandler mDragInputHandler = new InputHandler() { + + final class DragInputEventReceiver extends InputEventReceiver { + public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleInputEvent(InputEvent event, - InputQueue.FinishedCallback finishedCallback) { + public void onInputEvent(InputEvent event) { boolean handled = false; try { if (event instanceof MotionEvent @@ -625,10 +628,10 @@ public class WindowManagerService extends IWindowManager.Stub } catch (Exception e) { Slog.e(TAG, "Exception caught by drag handleMotion", e); } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } - }; + } /** * Whether the UI is currently running in touch mode (not showing @@ -9380,11 +9383,13 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler, + public FakeWindow addFakeWindow(Looper looper, + InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { synchronized (mWindowMap) { - FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputHandler, name, windowType, + FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputEventReceiverFactory, + name, windowType, layoutParamsFlags, canReceiveKeys, hasFocus, touchFullscreen); int i=0; while (i<mFakeWindows.size()) { |