Skip to content

Commit 072ec96

Browse files
author
Jeff Brown
committed
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
1 parent 1adee11 commit 072ec96

File tree

11 files changed

+557
-206
lines changed

11 files changed

+557
-206
lines changed

core/java/android/view/InputEventReceiver.java

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import android.os.Looper;
2222
import android.os.MessageQueue;
2323
import android.util.Log;
24+
import android.util.SparseIntArray;
2425

2526
/**
2627
* Provides a low-level mechanism for an application to receive input events.
@@ -38,13 +39,14 @@ public abstract class InputEventReceiver {
3839
private InputChannel mInputChannel;
3940
private MessageQueue mMessageQueue;
4041

41-
// The sequence number of the event that is in progress.
42-
private int mEventSequenceNumberInProgress = -1;
42+
// Map from InputEvent sequence numbers to dispatcher sequence numbers.
43+
private final SparseIntArray mSeqMap = new SparseIntArray();
4344

4445
private static native int nativeInit(InputEventReceiver receiver,
4546
InputChannel inputChannel, MessageQueue messageQueue);
4647
private static native void nativeDispose(int receiverPtr);
47-
private static native void nativeFinishInputEvent(int receiverPtr, boolean handled);
48+
private static native void nativeFinishInputEvent(int receiverPtr, int seq, boolean handled);
49+
private static native void nativeConsumeBatchedInputEvents(int receiverPtr);
4850

4951
/**
5052
* Creates an input event receiver bound to the specified input channel.
@@ -103,37 +105,74 @@ public void onInputEvent(InputEvent event) {
103105
finishInputEvent(event, false);
104106
}
105107

108+
/**
109+
* Called when a batched input event is pending.
110+
*
111+
* The batched input event will continue to accumulate additional movement
112+
* samples until the recipient calls {@link #consumeBatchedInputEvents} or
113+
* an event is received that ends the batch and causes it to be consumed
114+
* immediately (such as a pointer up event).
115+
*/
116+
public void onBatchedInputEventPending() {
117+
consumeBatchedInputEvents();
118+
}
119+
106120
/**
107121
* Finishes an input event and indicates whether it was handled.
122+
* Must be called on the same Looper thread to which the receiver is attached.
108123
*
109124
* @param event The input event that was finished.
110125
* @param handled True if the event was handled.
111126
*/
112-
public void finishInputEvent(InputEvent event, boolean handled) {
127+
public final void finishInputEvent(InputEvent event, boolean handled) {
113128
if (event == null) {
114129
throw new IllegalArgumentException("event must not be null");
115130
}
116131
if (mReceiverPtr == 0) {
117132
Log.w(TAG, "Attempted to finish an input event but the input event "
118133
+ "receiver has already been disposed.");
119134
} else {
120-
if (event.getSequenceNumber() != mEventSequenceNumberInProgress) {
135+
int index = mSeqMap.indexOfKey(event.getSequenceNumber());
136+
if (index < 0) {
121137
Log.w(TAG, "Attempted to finish an input event that is not in progress.");
122138
} else {
123-
mEventSequenceNumberInProgress = -1;
124-
nativeFinishInputEvent(mReceiverPtr, handled);
139+
int seq = mSeqMap.valueAt(index);
140+
mSeqMap.removeAt(index);
141+
nativeFinishInputEvent(mReceiverPtr, seq, handled);
125142
}
126143
}
127144
event.recycleIfNeededAfterDispatch();
128145
}
129146

147+
/**
148+
* Consumes all pending batched input events.
149+
* Must be called on the same Looper thread to which the receiver is attached.
150+
*
151+
* This method forces all batched input events to be delivered immediately.
152+
* Should be called just before animating or drawing a new frame in the UI.
153+
*/
154+
public final void consumeBatchedInputEvents() {
155+
if (mReceiverPtr == 0) {
156+
Log.w(TAG, "Attempted to consume batched input events but the input event "
157+
+ "receiver has already been disposed.");
158+
} else {
159+
nativeConsumeBatchedInputEvents(mReceiverPtr);
160+
}
161+
}
162+
130163
// Called from native code.
131164
@SuppressWarnings("unused")
132-
private void dispatchInputEvent(InputEvent event) {
133-
mEventSequenceNumberInProgress = event.getSequenceNumber();
165+
private void dispatchInputEvent(int seq, InputEvent event) {
166+
mSeqMap.put(event.getSequenceNumber(), seq);
134167
onInputEvent(event);
135168
}
136169

170+
// Called from native code.
171+
@SuppressWarnings("unused")
172+
private void dispatchBatchedInputEventPending() {
173+
onBatchedInputEventPending();
174+
}
175+
137176
public static interface Factory {
138177
public InputEventReceiver createInputEventReceiver(
139178
InputChannel inputChannel, Looper looper);

core/java/android/view/ViewRootImpl.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,11 @@ public void unscheduleTraversals() {
851851

852852
@Override
853853
public void onDraw() {
854+
if (mInputEventReceiver != null) {
855+
mInputEventReceiver.consumeBatchedInputEvents();
856+
}
857+
doProcessInputEvents();
858+
854859
if (mTraversalScheduled) {
855860
mTraversalScheduled = false;
856861
doTraversal();
@@ -891,8 +896,6 @@ public void requestTransitionStart(LayoutTransition transition) {
891896
}
892897

893898
private void doTraversal() {
894-
doProcessInputEvents();
895-
896899
if (mProfile) {
897900
Debug.startMethodTracing("ViewAncestor");
898901
}
@@ -3929,6 +3932,11 @@ public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
39293932
public void onInputEvent(InputEvent event) {
39303933
enqueueInputEvent(event, this, 0, true);
39313934
}
3935+
3936+
@Override
3937+
public void onBatchedInputEventPending() {
3938+
mChoreographer.scheduleDraw();
3939+
}
39323940
}
39333941
WindowInputEventReceiver mInputEventReceiver;
39343942

core/jni/android_app_NativeActivity.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ int32_t AInputQueue::getEvent(AInputEvent** outEvent) {
172172
in_flight_event inflight;
173173
inflight.event = kevent;
174174
inflight.seq = -1;
175-
inflight.doFinish = false;
175+
inflight.finishSeq = 0;
176176
mInFlightEvents.push(inflight);
177177
}
178178
if (mFinishPreDispatches.size() > 0) {
@@ -201,19 +201,21 @@ int32_t AInputQueue::getEvent(AInputEvent** outEvent) {
201201
}
202202
}
203203

204+
uint32_t consumerSeq;
204205
InputEvent* myEvent = NULL;
205-
status_t res = mConsumer.consume(this, &myEvent);
206+
status_t res = mConsumer.consume(this, true /*consumeBatches*/, &consumerSeq, &myEvent);
206207
if (res != android::OK) {
207-
ALOGW("channel '%s' ~ Failed to consume input event. status=%d",
208-
mConsumer.getChannel()->getName().string(), res);
209-
mConsumer.sendFinishedSignal(false);
208+
if (res != android::WOULD_BLOCK) {
209+
ALOGW("channel '%s' ~ Failed to consume input event. status=%d",
210+
mConsumer.getChannel()->getName().string(), res);
211+
}
210212
return -1;
211213
}
212214

213215
in_flight_event inflight;
214216
inflight.event = myEvent;
215217
inflight.seq = -1;
216-
inflight.doFinish = true;
218+
inflight.finishSeq = consumerSeq;
217219
mInFlightEvents.push(inflight);
218220

219221
*outEvent = myEvent;
@@ -255,8 +257,8 @@ void AInputQueue::finishEvent(AInputEvent* event, bool handled, bool didDefaultH
255257
for (size_t i=0; i<N; i++) {
256258
const in_flight_event& inflight(mInFlightEvents[i]);
257259
if (inflight.event == event) {
258-
if (inflight.doFinish) {
259-
int32_t res = mConsumer.sendFinishedSignal(handled);
260+
if (inflight.finishSeq) {
261+
status_t res = mConsumer.sendFinishedSignal(inflight.finishSeq, handled);
260262
if (res != android::OK) {
261263
ALOGW("Failed to send finished signal on channel '%s'. status=%d",
262264
mConsumer.getChannel()->getName().string(), res);

0 commit comments

Comments
 (0)