Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 072ec96a authored by Jeff Brown's avatar Jeff Brown
Browse files

Implement batching of input events on the consumer side.

To support this feature, the input dispatcher now allows input
events to be acknowledged out-of-order.  As a result, the
consumer can choose to defer handling an input event from one
device (because it is building a big batch) while continuing
to handle input events from other devices.

The InputEventReceiver now sends a notification when a batch
is pending.  The ViewRoot handles this notification by scheduling
a draw on the next sync.  When the draw happens, the InputEventReceiver
is instructed to consume all pending batched input events, the
input event queue is fully processed (as much as possible),
and then the ViewRoot performs traversals as usual.

With these changes in place, the input dispatch latency is
consistently less than one frame as long as the application itself
isn't stalled.  Input events are delivered to the application
as soon as possible and are handled as soon as possible.  In practice,
it is no longer possible for an application to build up a huge
backlog of touch events.

This is part of a series of changes to improve input system pipelining.

Bug: 5963420

Change-Id: I42c01117eca78f12d66d49a736c1c122346ccd1d
parent 1adee11b
Loading
Loading
Loading
Loading
+48 −9
Original line number Diff line number Diff line
@@ -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.
@@ -103,13 +105,26 @@ public abstract class InputEventReceiver {
        finishInputEvent(event, false);
    }

    /**
     * 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);
+10 −2
Original line number Diff line number Diff line
@@ -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;

+10 −8
Original line number Diff line number Diff line
@@ -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) {
        if (res != android::WOULD_BLOCK) {
            ALOGW("channel '%s' ~ Failed to consume input event.  status=%d",
                    mConsumer.getChannel()->getName().string(), res);
        mConsumer.sendFinishedSignal(false);
        }
        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);
+112 −80
Original line number Diff line number Diff line
@@ -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());
#endif
        mEventInProgress = false;

        status_t status = mInputConsumer.sendFinishedSignal(handled);
    status_t status = mInputConsumer.sendFinishedSignal(seq, 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;
    }
}

int NativeInputEventReceiver::handleReceiveCallback(int receiveFd, int events, void* data) {
@@ -132,28 +126,59 @@ 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;
}

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();
    for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
    status_t status = r->mInputConsumer.consume(&r->mInputEventFactory, &inputEvent);
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, &seq, &inputEvent);
        if (status) {
        ALOGW("channel '%s' ~ Failed to consume input event.  status=%d",
                r->getInputChannelName(), status);
        r->mInputConsumer.sendFinishedSignal(false);
        return 1;
            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' ~ Dispatching batched input event pending notification.",
                            getInputChannelName());
#endif
                    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);

    JNIEnv* env = AndroidRuntime::getJNIEnv();
        jobject inputEventObj;
        switch (inputEvent->getType()) {
        case AINPUT_EVENT_TYPE_KEY:
#if DEBUG_DISPATCH_CYCLE
        ALOGD("channel '%s' ~ Received key event.",
                r->getInputChannelName());
            ALOGD("channel '%s' ~ Received key event.", getInputChannelName());
#endif
            inputEventObj = android_view_KeyEvent_fromNative(env,
                    static_cast<KeyEvent*>(inputEvent));
@@ -161,8 +186,7 @@ int NativeInputEventReceiver::handleReceiveCallback(int receiveFd, int events, v

        case AINPUT_EVENT_TYPE_MOTION:
#if DEBUG_DISPATCH_CYCLE
        ALOGD("channel '%s' ~ Received motion event.",
                r->getInputChannelName());
            ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());
#endif
            inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
                    static_cast<MotionEvent*>(inputEvent));
@@ -174,37 +198,29 @@ int NativeInputEventReceiver::handleReceiveCallback(int receiveFd, int events, v
        }

        if (!inputEventObj) {
        ALOGW("channel '%s' ~ Failed to obtain event object.",
                r->getInputChannelName());
        r->mInputConsumer.sendFinishedSignal(false);
        return 1;
            ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName());
            mInputConsumer.sendFinishedSignal(seq, false);
            return NO_MEMORY;
        }

    r->mEventInProgress = true;

#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);

        env->DeleteLocalRef(inputEventObj);

        if (env->ExceptionCheck()) {
            ALOGE("channel '%s' ~ An exception occurred while dispatching an event.",
                r->getInputChannelName());
                    getInputChannelName());
            LOGE_EX(env);
            env->ExceptionClear();

        if (r->mEventInProgress) {
            r->mInputConsumer.sendFinishedSignal(false);
            r->mEventInProgress = false;
            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;
}

+2 −2
Original line number Diff line number Diff line
@@ -114,8 +114,8 @@ private:

    struct in_flight_event {
        android::InputEvent* event;
        int seq;
        bool doFinish;
        int seq; // internal sequence number for synthetic pre-dispatch events
        uint32_t finishSeq; // sequence number for sendFinishedSignal, or 0 if finish not required
    };

    struct finish_pre_dispatch {
Loading