Resample touch events on frame boundaries.

Bug: 6375101
Change-Id: I8774e366306bb2b6b4e42b913525bf25b0380ec3
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 6a457ec..9c56782 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -46,7 +46,8 @@
             InputChannel inputChannel, MessageQueue messageQueue);
     private static native void nativeDispose(int receiverPtr);
     private static native void nativeFinishInputEvent(int receiverPtr, int seq, boolean handled);
-    private static native void nativeConsumeBatchedInputEvents(int receiverPtr);
+    private static native void nativeConsumeBatchedInputEvents(int receiverPtr,
+            long frameTimeNanos);
 
     /**
      * Creates an input event receiver bound to the specified input channel.
@@ -114,7 +115,7 @@
      * immediately (such as a pointer up event).
      */
     public void onBatchedInputEventPending() {
-        consumeBatchedInputEvents();
+        consumeBatchedInputEvents(-1);
     }
 
     /**
@@ -150,13 +151,16 @@
      *
      * 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.
+     *
+     * @param frameTimeNanos The time in the {@link System#nanoTime()} time base
+     * when the current display frame started rendering, or -1 if unknown.
      */
-    public final void consumeBatchedInputEvents() {
+    public final void consumeBatchedInputEvents(long frameTimeNanos) {
         if (mReceiverPtr == 0) {
             Log.w(TAG, "Attempted to consume batched input events but the input event "
                     + "receiver has already been disposed.");
         } else {
-            nativeConsumeBatchedInputEvents(mReceiverPtr);
+            nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index afa9b12..1ee7934 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4204,11 +4204,11 @@
         }
     }
 
-    void doConsumeBatchedInput() {
+    void doConsumeBatchedInput(long frameTimeNanos) {
         if (mConsumeBatchedInputScheduled) {
             mConsumeBatchedInputScheduled = false;
             if (mInputEventReceiver != null) {
-                mInputEventReceiver.consumeBatchedInputEvents();
+                mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos);
             }
             doProcessInputEvents();
         }
@@ -4248,7 +4248,7 @@
     final class ConsumeBatchedInputRunnable implements Runnable {
         @Override
         public void run() {
-            doConsumeBatchedInput();
+            doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
         }
     }
     final ConsumeBatchedInputRunnable mConsumedBatchedInputRunnable =
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 19bc154..074afa3 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -207,7 +207,7 @@
 
     uint32_t consumerSeq;
     InputEvent* myEvent = NULL;
-    status_t res = mConsumer.consume(&mPooledInputEventFactory, true /*consumeBatches*/,
+    status_t res = mConsumer.consume(&mPooledInputEventFactory, true /*consumeBatches*/, -1,
             &consumerSeq, &myEvent);
     if (res != android::OK) {
         if (res != android::WOULD_BLOCK) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 8f6f5f4..08e08b9 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -52,7 +52,7 @@
 
     status_t initialize();
     status_t finishInputEvent(uint32_t seq, bool handled);
-    status_t consumeEvents(JNIEnv* env, bool consumeBatches);
+    status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime);
 
 protected:
     virtual ~NativeInputEventReceiver();
@@ -130,15 +130,16 @@
     }
 
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    status_t status = r->consumeEvents(env, false /*consumeBatches*/);
+    status_t status = r->consumeEvents(env, false /*consumeBatches*/, -1);
     r->mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
     return status == OK || status == NO_MEMORY ? 1 : 0;
 }
 
-status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches) {
+status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
+        bool consumeBatches, nsecs_t frameTime) {
 #if DEBUG_DISPATCH_CYCLE
-    ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s.", getInputChannelName(),
-            consumeBatches ? "true" : "false");
+    ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%lld.",
+            getInputChannelName(), consumeBatches ? "true" : "false", frameTime);
 #endif
 
     if (consumeBatches) {
@@ -150,7 +151,7 @@
         uint32_t seq;
         InputEvent* inputEvent;
         status_t status = mInputConsumer.consume(&mInputEventFactory,
-                consumeBatches, &seq, &inputEvent);
+                consumeBatches, frameTime, &seq, &inputEvent);
         if (status) {
             if (status == WOULD_BLOCK) {
                 if (!skipCallbacks && !mBatchedInputEventPending
@@ -270,10 +271,11 @@
     }
 }
 
-static void nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jint receiverPtr) {
+static void nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jint receiverPtr,
+        jlong frameTimeNanos) {
     sp<NativeInputEventReceiver> receiver =
             reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
-    status_t status = receiver->consumeEvents(env, true /*consumeBatches*/);
+    status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos);
     if (status && status != DEAD_OBJECT && !env->ExceptionCheck()) {
         String8 message;
         message.appendFormat("Failed to consume batched input event.  status=%d", status);
@@ -291,7 +293,7 @@
             (void*)nativeDispose },
     { "nativeFinishInputEvent", "(IIZ)V",
             (void*)nativeFinishInputEvent },
-    { "nativeConsumeBatchedInputEvents", "(I)V",
+    { "nativeConsumeBatchedInputEvents", "(IJ)V",
             (void*)nativeConsumeBatchedInputEvents },
 };
 
diff --git a/include/androidfw/Input.h b/include/androidfw/Input.h
index a98e1a2..044f2bf 100644
--- a/include/androidfw/Input.h
+++ b/include/androidfw/Input.h
@@ -208,6 +208,7 @@
     status_t setAxisValue(int32_t axis, float value);
 
     void scale(float scale);
+    void lerp(const PointerCoords& a, const PointerCoords& b, float alpha);
 
     inline float getX() const {
         return getAxisValue(AMOTION_EVENT_AXIS_X);
diff --git a/include/androidfw/InputTransport.h b/include/androidfw/InputTransport.h
index 29c296e..2924505 100644
--- a/include/androidfw/InputTransport.h
+++ b/include/androidfw/InputTransport.h
@@ -33,6 +33,7 @@
 #include <utils/RefBase.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
+#include <utils/BitSet.h>
 
 namespace android {
 
@@ -271,6 +272,9 @@
      * If consumeBatches is true, then events are still batched but they are consumed
      * immediately as soon as the input channel is exhausted.
      *
+     * The frameTime parameter specifies the time when the current display frame started
+     * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
+     *
      * The returned sequence number is never 0 unless the operation failed.
      *
      * Returns OK on success.
@@ -280,7 +284,7 @@
      * Other errors probably indicate that the channel is broken.
      */
     status_t consume(InputEventFactoryInterface* factory, bool consumeBatches,
-            uint32_t* outSeq, InputEvent** outEvent);
+            nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
 
     /* Sends a finished signal to the publisher to inform it that the message
      * with the specified sequence number has finished being process and whether
@@ -298,7 +302,7 @@
      * has a deferred event to be processed.  Deferred events are somewhat special in
      * that they have already been removed from the input channel.  If the input channel
      * becomes empty, the client may need to do extra work to ensure that it processes
-     * the deferred event despite the fact that the inptu channel's file descriptor
+     * the deferred event despite the fact that the input channel's file descriptor
      * is not readable.
      *
      * One option is simply to call consume() in a loop until it returns WOULD_BLOCK.
@@ -329,11 +333,55 @@
 
     // Batched motion events per device and source.
     struct Batch {
-        uint32_t seq; // sequence number of last input message batched in the event
-        MotionEvent event;
+        Vector<InputMessage> samples;
     };
     Vector<Batch> mBatches;
 
+    // Touch state per device and source, only for sources of class pointer.
+    struct History {
+        nsecs_t eventTime;
+        BitSet32 idBits;
+        PointerCoords pointers[MAX_POINTERS];
+
+        void initializeFrom(const InputMessage* msg) {
+            eventTime = msg->body.motion.eventTime;
+            idBits.clear();
+            for (size_t i = 0; i < msg->body.motion.pointerCount; i++) {
+                uint32_t id = msg->body.motion.pointers[i].properties.id;
+                idBits.markBit(id);
+                size_t index = idBits.getIndexOfBit(id);
+                pointers[index].copyFrom(msg->body.motion.pointers[i].coords);
+            }
+        }
+    };
+    struct TouchState {
+        int32_t deviceId;
+        int32_t source;
+        size_t historyCurrent;
+        size_t historySize;
+        History history[2];
+
+        void initialize(int32_t deviceId, int32_t source) {
+            this->deviceId = deviceId;
+            this->source = source;
+            historyCurrent = 0;
+            historySize = 0;
+        }
+
+        void addHistory(const InputMessage* msg) {
+            historyCurrent ^= 1;
+            if (historySize < 2) {
+                historySize += 1;
+            }
+            history[historyCurrent].initializeFrom(msg);
+        }
+
+        const History* getHistory(size_t index) const {
+            return &history[(historyCurrent + index) & 1];
+        }
+    };
+    Vector<TouchState> mTouchStates;
+
     // Chain of batched sequence numbers.  When multiple input messages are combined into
     // a batch, we append a record here that associates the last sequence number in the
     // batch with the previous one.  When the finished signal is sent, we traverse the
@@ -344,13 +392,26 @@
     };
     Vector<SeqChain> mSeqChains;
 
+    status_t consumeBatch(InputEventFactoryInterface* factory,
+            nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
+    status_t consumeSamples(InputEventFactoryInterface* factory,
+            Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent);
+
+    void updateTouchState(InputMessage* msg);
+    void resampleTouchState(nsecs_t frameTime, MotionEvent* event,
+            const InputMessage *next);
+
     ssize_t findBatch(int32_t deviceId, int32_t source) const;
+    ssize_t findTouchState(int32_t deviceId, int32_t source) const;
+
     status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
 
     static void initializeKeyEvent(KeyEvent* event, const InputMessage* msg);
     static void initializeMotionEvent(MotionEvent* event, const InputMessage* msg);
-    static bool canAppendSamples(const MotionEvent* event, const InputMessage* msg);
-    static void appendSamples(MotionEvent* event, const InputMessage* msg);
+    static void addSample(MotionEvent* event, const InputMessage* msg);
+    static bool canAddSample(const Batch& batch, const InputMessage* msg);
+    static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
+    static bool shouldResampleTool(int32_t toolType);
 };
 
 } // namespace android
diff --git a/libs/androidfw/Input.cpp b/libs/androidfw/Input.cpp
index 1617a3f..fbe1926 100644
--- a/libs/androidfw/Input.cpp
+++ b/libs/androidfw/Input.cpp
@@ -229,6 +229,26 @@
     scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MINOR, scaleFactor);
 }
 
+void PointerCoords::lerp(const PointerCoords& a, const PointerCoords& b, float alpha) {
+    bits = 0;
+    for (uint64_t bitsRemaining = a.bits | b.bits; bitsRemaining; ) {
+        int32_t axis = __builtin_ctz(bitsRemaining);
+        uint64_t axisBit = 1LL << axis;
+        bitsRemaining &= ~axisBit;
+        if (a.bits & axisBit) {
+            if (b.bits & axisBit) {
+                float aval = a.getAxisValue(axis);
+                float bval = b.getAxisValue(axis);
+                setAxisValue(axis, aval + alpha * (bval - aval));
+            } else {
+                setAxisValue(axis, a.getAxisValue(axis));
+            }
+        } else {
+            setAxisValue(axis, b.getAxisValue(axis));
+        }
+    }
+}
+
 #ifdef HAVE_ANDROID_OS
 status_t PointerCoords::readFromParcel(Parcel* parcel) {
     bits = parcel->readInt64();
diff --git a/libs/androidfw/InputTransport.cpp b/libs/androidfw/InputTransport.cpp
index 294236f..9a4182c 100644
--- a/libs/androidfw/InputTransport.cpp
+++ b/libs/androidfw/InputTransport.cpp
@@ -16,6 +16,9 @@
 // Log debug messages about transport actions
 #define DEBUG_TRANSPORT_ACTIONS 0
 
+// Log debug messages about touch event resampling
+#define DEBUG_RESAMPLING 0
+
 
 #include <cutils/log.h>
 #include <errno.h>
@@ -24,6 +27,7 @@
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <math.h>
 
 
 namespace android {
@@ -34,6 +38,20 @@
 // behind processing touches.
 static const size_t SOCKET_BUFFER_SIZE = 32 * 1024;
 
+// Nanoseconds per milliseconds.
+static const nsecs_t NANOS_PER_MS = 1000000;
+
+// Latency added during resampling.  A few milliseconds doesn't hurt much but
+// reduces the impact of mispredicted touch positions.
+static const nsecs_t RESAMPLE_LATENCY = 4 * NANOS_PER_MS;
+
+// Minimum time difference between consecutive samples before attempting to resample.
+static const nsecs_t RESAMPLE_MIN_DELTA = 1 * NANOS_PER_MS;
+
+// Maximum linear interpolation scale value.  The larger this is, the more error may
+// potentially be introduced.
+static const float RESAMPLE_MAX_ALPHA = 2.0f;
+
 
 // --- InputMessage ---
 
@@ -341,10 +359,10 @@
 }
 
 status_t InputConsumer::consume(InputEventFactoryInterface* factory,
-        bool consumeBatches, uint32_t* outSeq, InputEvent** outEvent) {
+        bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
 #if DEBUG_TRANSPORT_ACTIONS
-    ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s",
-            mChannel->getName().string(), consumeBatches ? "true" : "false");
+    ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%lld",
+            mChannel->getName().string(), consumeBatches ? "true" : "false", frameTime);
 #endif
 
     *outSeq = 0;
@@ -362,20 +380,15 @@
             status_t result = mChannel->receiveMessage(&mMsg);
             if (result) {
                 // Consume the next batched event unless batches are being held for later.
-                if (!mBatches.isEmpty() && (consumeBatches || result != WOULD_BLOCK)) {
-                    MotionEvent* motionEvent = factory->createMotionEvent();
-                    if (! motionEvent) return NO_MEMORY;
-
-                    const Batch& batch = mBatches.top();
-                    motionEvent->copyFrom(&batch.event, true /*keepHistory*/);
-                    *outSeq = batch.seq;
-                    *outEvent = motionEvent;
-                    mBatches.pop();
+                if (consumeBatches || result != WOULD_BLOCK) {
+                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
+                    if (*outEvent) {
 #if DEBUG_TRANSPORT_ACTIONS
-                    ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
-                            mChannel->getName().string(), *outSeq);
+                        ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
+                                mChannel->getName().string(), *outSeq);
 #endif
-                    break;
+                        break;
+                    }
                 }
                 return result;
             }
@@ -400,35 +413,23 @@
             ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
             if (batchIndex >= 0) {
                 Batch& batch = mBatches.editItemAt(batchIndex);
-                if (canAppendSamples(&batch.event, &mMsg)) {
-                    // Append to the batch and save the new sequence number for the tail end.
-                    uint32_t chain = batch.seq;
-                    appendSamples(&batch.event, &mMsg);
-                    batch.seq = mMsg.body.motion.seq;
-
-                    // Update the sequence number chain.
-                    SeqChain seqChain;
-                    seqChain.seq = batch.seq;
-                    seqChain.chain = chain;
-                    mSeqChains.push(seqChain);
+                if (canAddSample(batch, &mMsg)) {
+                    batch.samples.push(mMsg);
 #if DEBUG_TRANSPORT_ACTIONS
                     ALOGD("channel '%s' consumer ~ appended to batch event",
                             mChannel->getName().string());
 #endif
                     break;
                 } else {
-                    MotionEvent* motionEvent = factory->createMotionEvent();
-                    if (! motionEvent) return NO_MEMORY;
-
                     // We cannot append to the batch in progress, so we need to consume
                     // the previous batch right now and defer the new message until later.
                     mMsgDeferred = true;
-
-                    // Return the end of the previous batch.
-                    motionEvent->copyFrom(&batch.event, true /*keepHistory*/);
-                    *outSeq = batch.seq;
-                    *outEvent = motionEvent;
+                    status_t result = consumeSamples(factory,
+                            batch, batch.samples.size(), outSeq, outEvent);
                     mBatches.removeAt(batchIndex);
+                    if (result) {
+                        return result;
+                    }
 #if DEBUG_TRANSPORT_ACTIONS
                     ALOGD("channel '%s' consumer ~ consumed batch event and "
                             "deferred current event, seq=%u",
@@ -443,8 +444,7 @@
                     || mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
                 mBatches.push();
                 Batch& batch = mBatches.editTop();
-                batch.seq = mMsg.body.motion.seq;
-                initializeMotionEvent(&batch.event, &mMsg);
+                batch.samples.push(mMsg);
 #if DEBUG_TRANSPORT_ACTIONS
                 ALOGD("channel '%s' consumer ~ started batch event",
                         mChannel->getName().string());
@@ -455,6 +455,7 @@
             MotionEvent* motionEvent = factory->createMotionEvent();
             if (! motionEvent) return NO_MEMORY;
 
+            updateTouchState(&mMsg);
             initializeMotionEvent(motionEvent, &mMsg);
             *outSeq = mMsg.body.motion.seq;
             *outEvent = motionEvent;
@@ -474,6 +475,213 @@
     return OK;
 }
 
+status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory,
+        nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
+    status_t result;
+    for (size_t i = mBatches.size(); i-- > 0; ) {
+        Batch& batch = mBatches.editItemAt(i);
+        if (frameTime < 0) {
+            result = consumeSamples(factory, batch, batch.samples.size(),
+                    outSeq, outEvent);
+            mBatches.removeAt(i);
+            return result;
+        }
+
+        nsecs_t sampleTime = frameTime - RESAMPLE_LATENCY;
+        ssize_t split = findSampleNoLaterThan(batch, sampleTime);
+        if (split < 0) {
+            continue;
+        }
+
+        result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
+        const InputMessage* next;
+        if (batch.samples.isEmpty()) {
+            mBatches.removeAt(i);
+            next = NULL;
+        } else {
+            next = &batch.samples.itemAt(0);
+        }
+        if (!result) {
+            resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
+        }
+        return result;
+    }
+
+    return WOULD_BLOCK;
+}
+
+status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory,
+        Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent) {
+    MotionEvent* motionEvent = factory->createMotionEvent();
+    if (! motionEvent) return NO_MEMORY;
+
+    uint32_t chain = 0;
+    for (size_t i = 0; i < count; i++) {
+        InputMessage& msg = batch.samples.editItemAt(i);
+        updateTouchState(&msg);
+        if (i) {
+            SeqChain seqChain;
+            seqChain.seq = msg.body.motion.seq;
+            seqChain.chain = chain;
+            mSeqChains.push(seqChain);
+            addSample(motionEvent, &msg);
+        } else {
+            initializeMotionEvent(motionEvent, &msg);
+        }
+        chain = msg.body.motion.seq;
+    }
+    batch.samples.removeItemsAt(0, count);
+
+    *outSeq = chain;
+    *outEvent = motionEvent;
+    return OK;
+}
+
+void InputConsumer::updateTouchState(InputMessage* msg) {
+    if (!(msg->body.motion.source & AINPUT_SOURCE_CLASS_POINTER)) {
+        return;
+    }
+
+    int32_t deviceId = msg->body.motion.deviceId;
+    int32_t source = msg->body.motion.source;
+
+    // TODO: Filter the incoming touch event so that it aligns better
+    // with prior predictions.  Turning RESAMPLE_LATENCY offsets the need
+    // for filtering but it would be nice to reduce the latency further.
+
+    switch (msg->body.motion.action) {
+    case AMOTION_EVENT_ACTION_DOWN: {
+        ssize_t index = findTouchState(deviceId, source);
+        if (index < 0) {
+            mTouchStates.push();
+            index = mTouchStates.size() - 1;
+        }
+        TouchState& touchState = mTouchStates.editItemAt(index);
+        touchState.initialize(deviceId, source);
+        touchState.addHistory(msg);
+        break;
+    }
+
+    case AMOTION_EVENT_ACTION_MOVE: {
+        ssize_t index = findTouchState(deviceId, source);
+        if (index >= 0) {
+            TouchState& touchState = mTouchStates.editItemAt(index);
+            touchState.addHistory(msg);
+        }
+        break;
+    }
+
+    case AMOTION_EVENT_ACTION_UP:
+    case AMOTION_EVENT_ACTION_CANCEL: {
+        ssize_t index = findTouchState(deviceId, source);
+        if (index >= 0) {
+            mTouchStates.removeAt(index);
+        }
+        break;
+    }
+    }
+}
+
+void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
+    const InputMessage* next) {
+    if (event->getAction() != AMOTION_EVENT_ACTION_MOVE
+            || !(event->getSource() & AINPUT_SOURCE_CLASS_POINTER)) {
+#if DEBUG_RESAMPLING
+        ALOGD("Not resampled, not a move.");
+#endif
+        return;
+    }
+
+    ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
+    if (index < 0) {
+#if DEBUG_RESAMPLING
+        ALOGD("Not resampled, no touch state for device.");
+#endif
+        return;
+    }
+
+    TouchState& touchState = mTouchStates.editItemAt(index);
+    if (touchState.historySize < 1) {
+#if DEBUG_RESAMPLING
+        ALOGD("Not resampled, no history for device.");
+#endif
+        return;
+    }
+
+    const History* current = touchState.getHistory(0);
+    const History* other;
+    History future;
+    if (next) {
+        future.initializeFrom(next);
+        other = &future;
+    } else if (touchState.historySize >= 2) {
+        other = touchState.getHistory(1);
+    } else {
+#if DEBUG_RESAMPLING
+        ALOGD("Not resampled, insufficient data.");
+#endif
+        return;
+    }
+
+    nsecs_t delta = current->eventTime - other->eventTime;
+    if (delta > -RESAMPLE_MIN_DELTA && delta < RESAMPLE_MIN_DELTA) {
+#if DEBUG_RESAMPLING
+        ALOGD("Not resampled, delta time is %lld", delta);
+#endif
+        return;
+    }
+
+    float alpha = float(current->eventTime - sampleTime) / delta;
+    if (fabs(alpha) > RESAMPLE_MAX_ALPHA) {
+#if DEBUG_RESAMPLING
+        ALOGD("Not resampled, alpha is %f", alpha);
+#endif
+        return;
+    }
+
+    size_t pointerCount = event->getPointerCount();
+    PointerCoords resampledCoords[MAX_POINTERS];
+    for (size_t i = 0; i < pointerCount; i++) {
+        uint32_t id = event->getPointerId(i);
+        if (!current->idBits.hasBit(id)) {
+#if DEBUG_RESAMPLING
+            ALOGD("Not resampled, missing id %d", id);
+#endif
+            return;
+        }
+        const PointerCoords& currentCoords =
+                current->pointers[current->idBits.getIndexOfBit(id)];
+        if (other->idBits.hasBit(id)
+                && shouldResampleTool(event->getToolType(i))) {
+            const PointerCoords& otherCoords =
+                    other->pointers[other->idBits.getIndexOfBit(id)];
+            resampledCoords[i].lerp(currentCoords, otherCoords, alpha);
+#if DEBUG_RESAMPLING
+            ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
+                    "other (%0.3f, %0.3f), alpha %0.3f",
+                    i, resampledCoords[i].getX(), resampledCoords[i].getY(),
+                    currentCoords.getX(), currentCoords.getY(),
+                    otherCoords.getX(), otherCoords.getY(),
+                    alpha);
+#endif
+        } else {
+            resampledCoords[i].copyFrom(currentCoords);
+#if DEBUG_RESAMPLING
+            ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)",
+                    i, resampledCoords[i].getX(), resampledCoords[i].getY(),
+                    currentCoords.getX(), currentCoords.getY());
+#endif
+        }
+    }
+
+    event->addSample(sampleTime, resampledCoords);
+}
+
+bool InputConsumer::shouldResampleTool(int32_t toolType) {
+    return toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
+            || toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
 status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
 #if DEBUG_TRANSPORT_ACTIONS
     ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
@@ -538,7 +746,18 @@
 ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
     for (size_t i = 0; i < mBatches.size(); i++) {
         const Batch& batch = mBatches.itemAt(i);
-        if (batch.event.getDeviceId() == deviceId && batch.event.getSource() == source) {
+        const InputMessage& head = batch.samples.itemAt(0);
+        if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
+    for (size_t i = 0; i < mTouchStates.size(); i++) {
+        const TouchState& touchState = mTouchStates.itemAt(i);
+        if (touchState.deviceId == deviceId && touchState.source == source) {
             return i;
         }
     }
@@ -587,21 +806,7 @@
             pointerCoords);
 }
 
-bool InputConsumer::canAppendSamples(const MotionEvent* event, const InputMessage *msg) {
-    size_t pointerCount = msg->body.motion.pointerCount;
-    if (event->getPointerCount() != pointerCount
-            || event->getAction() != msg->body.motion.action) {
-        return false;
-    }
-    for (size_t i = 0; i < pointerCount; i++) {
-        if (*event->getPointerProperties(i) != msg->body.motion.pointers[i].properties) {
-            return false;
-        }
-    }
-    return true;
-}
-
-void InputConsumer::appendSamples(MotionEvent* event, const InputMessage* msg) {
+void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
     size_t pointerCount = msg->body.motion.pointerCount;
     PointerCoords pointerCoords[pointerCount];
     for (size_t i = 0; i < pointerCount; i++) {
@@ -612,4 +817,30 @@
     event->addSample(msg->body.motion.eventTime, pointerCoords);
 }
 
+bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) {
+    const InputMessage& head = batch.samples.itemAt(0);
+    size_t pointerCount = msg->body.motion.pointerCount;
+    if (head.body.motion.pointerCount != pointerCount
+            || head.body.motion.action != msg->body.motion.action) {
+        return false;
+    }
+    for (size_t i = 0; i < pointerCount; i++) {
+        if (head.body.motion.pointers[i].properties
+                != msg->body.motion.pointers[i].properties) {
+            return false;
+        }
+    }
+    return true;
+}
+
+ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
+    size_t numSamples = batch.samples.size();
+    size_t index = 0;
+    while (index < numSamples
+            && batch.samples.itemAt(index).body.motion.eventTime <= time) {
+        index += 1;
+    }
+    return ssize_t(index) - 1;
+}
+
 } // namespace android
diff --git a/libs/androidfw/tests/InputPublisherAndConsumer_test.cpp b/libs/androidfw/tests/InputPublisherAndConsumer_test.cpp
index 442b62f..f45774b 100644
--- a/libs/androidfw/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/androidfw/tests/InputPublisherAndConsumer_test.cpp
@@ -88,7 +88,7 @@
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, &consumeSeq, &event);
+    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status)
             << "consumer consume should return OK";
 
@@ -171,7 +171,7 @@
 
     uint32_t consumeSeq;
     InputEvent* event;
-    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, &consumeSeq, &event);
+    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
     ASSERT_EQ(OK, status)
             << "consumer consume should return OK";