diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/view/InputEventReceiver.java | 57 | ||||
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 12 | ||||
-rw-r--r-- | core/jni/android_app_NativeActivity.cpp | 18 | ||||
-rw-r--r-- | core/jni/android_view_InputEventReceiver.cpp | 192 |
4 files changed, 180 insertions, 99 deletions
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java index 764d8dc..6a457ec 100644 --- a/core/java/android/view/InputEventReceiver.java +++ b/core/java/android/view/InputEventReceiver.java @@ -21,6 +21,7 @@ import dalvik.system.CloseGuard; import android.os.Looper; import android.os.MessageQueue; import android.util.Log; +import android.util.SparseIntArray; /** * Provides a low-level mechanism for an application to receive input events. @@ -38,13 +39,14 @@ public abstract class InputEventReceiver { private InputChannel mInputChannel; private MessageQueue mMessageQueue; - // The sequence number of the event that is in progress. - private int mEventSequenceNumberInProgress = -1; + // Map from InputEvent sequence numbers to dispatcher sequence numbers. + private final SparseIntArray mSeqMap = new SparseIntArray(); 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); + private static native void nativeFinishInputEvent(int receiverPtr, int seq, boolean handled); + private static native void nativeConsumeBatchedInputEvents(int receiverPtr); /** * Creates an input event receiver bound to the specified input channel. @@ -104,12 +106,25 @@ public abstract class InputEventReceiver { } /** + * Called when a batched input event is pending. + * + * The batched input event will continue to accumulate additional movement + * samples until the recipient calls {@link #consumeBatchedInputEvents} or + * an event is received that ends the batch and causes it to be consumed + * immediately (such as a pointer up event). + */ + public void onBatchedInputEventPending() { + consumeBatchedInputEvents(); + } + + /** * Finishes an input event and indicates whether it was handled. + * Must be called on the same Looper thread to which the receiver is attached. * * @param event The input event that was finished. * @param handled True if the event was handled. */ - public void finishInputEvent(InputEvent event, boolean handled) { + public final void finishInputEvent(InputEvent event, boolean handled) { if (event == null) { throw new IllegalArgumentException("event must not be null"); } @@ -117,23 +132,47 @@ public abstract class InputEventReceiver { Log.w(TAG, "Attempted to finish an input event but the input event " + "receiver has already been disposed."); } else { - if (event.getSequenceNumber() != mEventSequenceNumberInProgress) { + int index = mSeqMap.indexOfKey(event.getSequenceNumber()); + if (index < 0) { Log.w(TAG, "Attempted to finish an input event that is not in progress."); } else { - mEventSequenceNumberInProgress = -1; - nativeFinishInputEvent(mReceiverPtr, handled); + int seq = mSeqMap.valueAt(index); + mSeqMap.removeAt(index); + nativeFinishInputEvent(mReceiverPtr, seq, handled); } } event.recycleIfNeededAfterDispatch(); } + /** + * Consumes all pending batched input events. + * Must be called on the same Looper thread to which the receiver is attached. + * + * This method forces all batched input events to be delivered immediately. + * Should be called just before animating or drawing a new frame in the UI. + */ + public final void consumeBatchedInputEvents() { + if (mReceiverPtr == 0) { + Log.w(TAG, "Attempted to consume batched input events but the input event " + + "receiver has already been disposed."); + } else { + nativeConsumeBatchedInputEvents(mReceiverPtr); + } + } + // Called from native code. @SuppressWarnings("unused") - private void dispatchInputEvent(InputEvent event) { - mEventSequenceNumberInProgress = event.getSequenceNumber(); + private void dispatchInputEvent(int seq, InputEvent event) { + mSeqMap.put(event.getSequenceNumber(), seq); onInputEvent(event); } + // Called from native code. + @SuppressWarnings("unused") + private void dispatchBatchedInputEventPending() { + onBatchedInputEventPending(); + } + public static interface Factory { public InputEventReceiver createInputEventReceiver( InputChannel inputChannel, Looper looper); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 940d4c6..1930a5e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -851,6 +851,11 @@ public final class ViewRootImpl extends Handler implements ViewParent, @Override public void onDraw() { + if (mInputEventReceiver != null) { + mInputEventReceiver.consumeBatchedInputEvents(); + } + doProcessInputEvents(); + if (mTraversalScheduled) { mTraversalScheduled = false; doTraversal(); @@ -891,8 +896,6 @@ public final class ViewRootImpl extends Handler implements ViewParent, } private void doTraversal() { - doProcessInputEvents(); - if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } @@ -3929,6 +3932,11 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void onInputEvent(InputEvent event) { enqueueInputEvent(event, this, 0, true); } + + @Override + public void onBatchedInputEventPending() { + mChoreographer.scheduleDraw(); + } } WindowInputEventReceiver mInputEventReceiver; diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp index f383c5d..536681b 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -172,7 +172,7 @@ int32_t AInputQueue::getEvent(AInputEvent** outEvent) { in_flight_event inflight; inflight.event = kevent; inflight.seq = -1; - inflight.doFinish = false; + inflight.finishSeq = 0; mInFlightEvents.push(inflight); } if (mFinishPreDispatches.size() > 0) { @@ -201,19 +201,21 @@ int32_t AInputQueue::getEvent(AInputEvent** outEvent) { } } + uint32_t consumerSeq; InputEvent* myEvent = NULL; - status_t res = mConsumer.consume(this, &myEvent); + status_t res = mConsumer.consume(this, true /*consumeBatches*/, &consumerSeq, &myEvent); if (res != android::OK) { - ALOGW("channel '%s' ~ Failed to consume input event. status=%d", - mConsumer.getChannel()->getName().string(), res); - mConsumer.sendFinishedSignal(false); + if (res != android::WOULD_BLOCK) { + ALOGW("channel '%s' ~ Failed to consume input event. status=%d", + mConsumer.getChannel()->getName().string(), res); + } return -1; } in_flight_event inflight; inflight.event = myEvent; inflight.seq = -1; - inflight.doFinish = true; + inflight.finishSeq = consumerSeq; mInFlightEvents.push(inflight); *outEvent = myEvent; @@ -255,8 +257,8 @@ void AInputQueue::finishEvent(AInputEvent* event, bool handled, bool didDefaultH for (size_t i=0; i<N; i++) { const in_flight_event& inflight(mInFlightEvents[i]); if (inflight.event == event) { - if (inflight.doFinish) { - int32_t res = mConsumer.sendFinishedSignal(handled); + if (inflight.finishSeq) { + status_t res = mConsumer.sendFinishedSignal(inflight.finishSeq, handled); if (res != android::OK) { ALOGW("Failed to send finished signal on channel '%s'. status=%d", mConsumer.getChannel()->getName().string(), res); diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index 23e1590..4b737ed 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -40,6 +40,7 @@ static struct { jclass clazz; jmethodID dispatchInputEvent; + jmethodID dispatchBatchedInputEventPending; } gInputEventReceiverClassInfo; @@ -50,7 +51,8 @@ public: const sp<Looper>& looper); status_t initialize(); - status_t finishInputEvent(bool handled); + status_t finishInputEvent(uint32_t seq, bool handled); + status_t consumeEvents(bool consumeBatches); static int handleReceiveCallback(int receiveFd, int events, void* data); protected: @@ -60,8 +62,8 @@ private: jobject mReceiverObjGlobal; InputConsumer mInputConsumer; sp<Looper> mLooper; - bool mEventInProgress; PreallocatedInputEventFactory mInputEventFactory; + bool mBatchedInputEventPending; const char* getInputChannelName() { return mInputConsumer.getChannel()->getName().string(); @@ -72,7 +74,8 @@ private: NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, jobject receiverObj, const sp<InputChannel>& inputChannel, const sp<Looper>& looper) : mReceiverObjGlobal(env->NewGlobalRef(receiverObj)), - mInputConsumer(inputChannel), mLooper(looper), mEventInProgress(false) { + mInputConsumer(inputChannel), mLooper(looper), + mBatchedInputEventPending(false) { #if DEBUG_DISPATCH_CYCLE ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName()); #endif @@ -84,9 +87,6 @@ NativeInputEventReceiver::~NativeInputEventReceiver() { #endif mLooper->removeFd(mInputConsumer.getChannel()->getFd()); - if (mEventInProgress) { - mInputConsumer.sendFinishedSignal(false); // ignoring result - } JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mReceiverObjGlobal); @@ -98,23 +98,17 @@ status_t NativeInputEventReceiver::initialize() { return OK; } -status_t NativeInputEventReceiver::finishInputEvent(bool handled) { - if (mEventInProgress) { +status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) { #if DEBUG_DISPATCH_CYCLE - ALOGD("channel '%s' ~ Finished input event.", getInputChannelName()); + ALOGD("channel '%s' ~ Finished input event.", getInputChannelName()); #endif - mEventInProgress = false; - status_t status = mInputConsumer.sendFinishedSignal(handled); - if (status) { - ALOGW("Failed to send finished signal on channel '%s'. status=%d", - getInputChannelName(), status); - } - return status; - } else { - ALOGW("Ignoring attempt to finish input event while no event is in progress."); - return OK; + status_t status = mInputConsumer.sendFinishedSignal(seq, handled); + if (status) { + ALOGW("Failed to send finished signal on channel '%s'. status=%d", + getInputChannelName(), status); } + return status; } int NativeInputEventReceiver::handleReceiveCallback(int receiveFd, int events, void* data) { @@ -132,79 +126,101 @@ int NativeInputEventReceiver::handleReceiveCallback(int receiveFd, int events, v return 1; } - if (r->mEventInProgress) { - ALOGW("channel '%s' ~ Publisher sent spurious dispatch signal.", - r->getInputChannelName()); - return 1; - } + status_t status = r->consumeEvents(false /*consumeBatches*/); + return status == OK || status == NO_MEMORY ? 1 : 0; +} - InputEvent* inputEvent; - status_t status = r->mInputConsumer.consume(&r->mInputEventFactory, &inputEvent); - if (status) { - ALOGW("channel '%s' ~ Failed to consume input event. status=%d", - r->getInputChannelName(), status); - r->mInputConsumer.sendFinishedSignal(false); - return 1; +status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s.", getInputChannelName(), + consumeBatches ? "true" : "false"); +#endif + + if (consumeBatches) { + mBatchedInputEventPending = false; } JNIEnv* env = AndroidRuntime::getJNIEnv(); - jobject inputEventObj; - switch (inputEvent->getType()) { - case AINPUT_EVENT_TYPE_KEY: + for (;;) { + uint32_t seq; + InputEvent* inputEvent; + status_t status = mInputConsumer.consume(&mInputEventFactory, + consumeBatches, &seq, &inputEvent); + if (status) { + if (status == WOULD_BLOCK) { + if (mInputConsumer.hasPendingBatch() && !mBatchedInputEventPending) { + // There is a pending batch. Come back later. + mBatchedInputEventPending = true; #if DEBUG_DISPATCH_CYCLE - ALOGD("channel '%s' ~ Received key event.", - r->getInputChannelName()); + ALOGD("channel '%s' ~ Dispatching batched input event pending notification.", + getInputChannelName()); #endif - inputEventObj = android_view_KeyEvent_fromNative(env, - static_cast<KeyEvent*>(inputEvent)); - break; + env->CallVoidMethod(mReceiverObjGlobal, + gInputEventReceiverClassInfo.dispatchBatchedInputEventPending); + + if (env->ExceptionCheck()) { + ALOGE("channel '%s' ~ An exception occurred while dispatching that " + "batched input events are pending.", getInputChannelName()); + LOGE_EX(env); + env->ExceptionClear(); + mBatchedInputEventPending = false; // try again later + } + } + return OK; + } + ALOGE("channel '%s' ~ Failed to consume input event. status=%d", + getInputChannelName(), status); + return status; + } + assert(inputEvent); - case AINPUT_EVENT_TYPE_MOTION: + jobject inputEventObj; + switch (inputEvent->getType()) { + case AINPUT_EVENT_TYPE_KEY: #if DEBUG_DISPATCH_CYCLE - ALOGD("channel '%s' ~ Received motion event.", - r->getInputChannelName()); + ALOGD("channel '%s' ~ Received key event.", getInputChannelName()); #endif - inputEventObj = android_view_MotionEvent_obtainAsCopy(env, - static_cast<MotionEvent*>(inputEvent)); - break; + inputEventObj = android_view_KeyEvent_fromNative(env, + static_cast<KeyEvent*>(inputEvent)); + break; - default: - assert(false); // InputConsumer should prevent this from ever happening - inputEventObj = NULL; - } + case AINPUT_EVENT_TYPE_MOTION: +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ Received motion event.", getInputChannelName()); +#endif + inputEventObj = android_view_MotionEvent_obtainAsCopy(env, + static_cast<MotionEvent*>(inputEvent)); + break; - if (!inputEventObj) { - ALOGW("channel '%s' ~ Failed to obtain event object.", - r->getInputChannelName()); - r->mInputConsumer.sendFinishedSignal(false); - return 1; - } + default: + assert(false); // InputConsumer should prevent this from ever happening + inputEventObj = NULL; + } - r->mEventInProgress = true; + if (!inputEventObj) { + ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName()); + mInputConsumer.sendFinishedSignal(seq, false); + return NO_MEMORY; + } #if DEBUG_DISPATCH_CYCLE - ALOGD("channel '%s' ~ Invoking input handler.", r->getInputChannelName()); -#endif - env->CallVoidMethod(r->mReceiverObjGlobal, - gInputEventReceiverClassInfo.dispatchInputEvent, inputEventObj); -#if DEBUG_DISPATCH_CYCLE - ALOGD("channel '%s' ~ Returned from input handler.", r->getInputChannelName()); + ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName()); #endif + env->CallVoidMethod(mReceiverObjGlobal, + gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); - if (env->ExceptionCheck()) { - ALOGE("channel '%s' ~ An exception occurred while dispatching an event.", - r->getInputChannelName()); - LOGE_EX(env); - env->ExceptionClear(); + env->DeleteLocalRef(inputEventObj); - if (r->mEventInProgress) { - r->mInputConsumer.sendFinishedSignal(false); - r->mEventInProgress = false; + if (env->ExceptionCheck()) { + ALOGE("channel '%s' ~ An exception occurred while dispatching an event.", + getInputChannelName()); + LOGE_EX(env); + env->ExceptionClear(); + + mInputConsumer.sendFinishedSignal(seq, false); + return OK; } } - - env->DeleteLocalRef(inputEventObj); - return 1; } @@ -243,10 +259,11 @@ static void nativeDispose(JNIEnv* env, jclass clazz, jint receiverPtr) { receiver->decStrong(gInputEventReceiverClassInfo.clazz); // drop reference held by the object } -static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr, jboolean handled) { +static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr, + jint seq, jboolean handled) { sp<NativeInputEventReceiver> receiver = reinterpret_cast<NativeInputEventReceiver*>(receiverPtr); - status_t status = receiver->finishInputEvent(handled); + status_t status = receiver->finishInputEvent(seq, handled); if (status) { String8 message; message.appendFormat("Failed to finish input event. status=%d", status); @@ -254,17 +271,29 @@ static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr, } } +static void nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jint receiverPtr) { + sp<NativeInputEventReceiver> receiver = + reinterpret_cast<NativeInputEventReceiver*>(receiverPtr); + status_t status = receiver->consumeEvents(true /*consumeBatches*/); + if (status) { + String8 message; + message.appendFormat("Failed to consume batched 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", + { "nativeDispose", "(I)V", (void*)nativeDispose }, - { "nativeFinishInputEvent", "(IZ)V", - (void*)nativeFinishInputEvent } + { "nativeFinishInputEvent", "(IIZ)V", + (void*)nativeFinishInputEvent }, + { "nativeConsumeBatchedInputEvents", "(I)V", + (void*)nativeConsumeBatchedInputEvents }, }; #define FIND_CLASS(var, className) \ @@ -285,7 +314,10 @@ int register_android_view_InputEventReceiver(JNIEnv* env) { GET_METHOD_ID(gInputEventReceiverClassInfo.dispatchInputEvent, gInputEventReceiverClassInfo.clazz, - "dispatchInputEvent", "(Landroid/view/InputEvent;)V"); + "dispatchInputEvent", "(ILandroid/view/InputEvent;)V"); + GET_METHOD_ID(gInputEventReceiverClassInfo.dispatchBatchedInputEventPending, + gInputEventReceiverClassInfo.clazz, + "dispatchBatchedInputEventPending", "()V"); return 0; } |