Merge changes I9fe98448,I41648d76 into main

* changes:
  InputTracer: Don't make WindowDispatchArgs an inner class
  InputTracer: Introduce TracedEventArgs to track secure events
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 082387e..fb1419f 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -236,6 +236,10 @@
  * it is acquired. If no acquire_fence_fd was provided, this timestamp will be set to -1.
  *
  * Available since API level 29.
+ *
+ * @deprecated This may return SIGNAL_PENDING because the stats can arrive before the acquire
+ * fence has signaled, depending on internal timing differences. Therefore the caller should
+ * use the acquire fence passed in to setBuffer and query the signal time.
  */
 int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* surface_transaction_stats,
                                                 ASurfaceControl* surface_control)
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
new file mode 100644
index 0000000..7e6ae9b
--- /dev/null
+++ b/include/input/InputConsumerNoResampling.h
@@ -0,0 +1,211 @@
+/**
+ * Copyright 2024 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.
+ */
+
+#pragma once
+
+#include <utils/Looper.h>
+#include "InputTransport.h"
+
+namespace android {
+
+/**
+ * An interface to receive batched input events. Even if you don't want batching, you still have to
+ * use this interface, and some of the events will be batched if your implementation is slow to
+ * handle the incoming input.
+ */
+class InputConsumerCallbacks {
+public:
+    virtual ~InputConsumerCallbacks(){};
+    virtual void onKeyEvent(KeyEvent&& event, uint32_t seq) = 0;
+    virtual void onMotionEvent(MotionEvent&& event, uint32_t seq) = 0;
+    /**
+     * When you receive this callback, you must (eventually) call "consumeBatchedInputEvents".
+     * If you don't want batching, then call "consumeBatchedInputEvents" immediately with
+     * std::nullopt frameTime to receive the pending motion event(s).
+     * @param pendingBatchSource the source of the pending batch.
+     */
+    virtual void onBatchedInputEventPending(int32_t pendingBatchSource) = 0;
+    virtual void onFocusEvent(FocusEvent&& event, uint32_t seq) = 0;
+    virtual void onCaptureEvent(CaptureEvent&& event, uint32_t seq) = 0;
+    virtual void onDragEvent(DragEvent&& event, uint32_t seq) = 0;
+    virtual void onTouchModeEvent(TouchModeEvent&& event, uint32_t seq) = 0;
+};
+
+/**
+ * Consumes input events from an input channel.
+ *
+ * This is a re-implementation of InputConsumer that does not have resampling at the current moment.
+ * A lot of the higher-level logic has been folded into this class, to make it easier to use.
+ * In the legacy class, InputConsumer, the consumption logic was partially handled in the jni layer,
+ * as well as various actions like adding the fd to the Choreographer.
+ *
+ * TODO(b/297226446): use this instead of "InputConsumer":
+ * - Add resampling to this class
+ * - Allow various resampling strategies to be specified
+ * - Delete the old "InputConsumer" and use this class instead, renaming it to "InputConsumer".
+ * - Add tracing
+ * - Update all tests to use the new InputConsumer
+ *
+ * This class is not thread-safe. We are currently allowing the constructor to run on any thread,
+ * but all of the remaining APIs should be invoked on the looper thread only.
+ */
+class InputConsumerNoResampling final {
+public:
+    explicit InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
+                                       sp<Looper> looper, InputConsumerCallbacks& callbacks);
+    ~InputConsumerNoResampling();
+
+    /**
+     * Must be called exactly once for each event received through the callbacks.
+     */
+    void finishInputEvent(uint32_t seq, bool handled);
+    void reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime);
+    /**
+     * If you want to consume all events immediately (disable batching), the you still must call
+     * this. For frameTime, use a std::nullopt.
+     * @param frameTime the time up to which consume the events. When there's double (or triple)
+     * buffering, you may want to not consume all events currently available, because you could be
+     * still working on an older frame, but there could already have been events that arrived that
+     * are more recent.
+     * @return whether any events were actually consumed
+     */
+    bool consumeBatchedInputEvents(std::optional<nsecs_t> frameTime);
+    /**
+     * Returns true when there is *likely* a pending batch or a pending event in the channel.
+     *
+     * This is only a performance hint and may return false negative results. Clients should not
+     * rely on availability of the message based on the return value.
+     */
+    bool probablyHasInput() const;
+
+    std::string getName() { return mChannel->getName(); }
+
+    std::string dump() const;
+
+private:
+    std::shared_ptr<InputChannel> mChannel;
+    sp<Looper> mLooper;
+    InputConsumerCallbacks& mCallbacks;
+
+    // Looper-related infrastructure
+    /**
+     * This class is needed to associate the function "handleReceiveCallback" with the provided
+     * looper. The callback sent to the looper is RefBase - based, so we can't just send a reference
+     * of this class directly to the looper.
+     */
+    class LooperEventCallback : public LooperCallback {
+    public:
+        LooperEventCallback(std::function<int(int events)> callback) : mCallback(callback) {}
+        int handleEvent(int /*fd*/, int events, void* /*data*/) override {
+            return mCallback(events);
+        }
+
+    private:
+        std::function<int(int events)> mCallback;
+    };
+    sp<LooperEventCallback> mCallback;
+    /**
+     * The actual code that executes when the looper encounters available data on the InputChannel.
+     */
+    int handleReceiveCallback(int events);
+    int mFdEvents;
+    void setFdEvents(int events);
+
+    void ensureCalledOnLooperThread(const char* func) const;
+
+    // Event-reading infrastructure
+    /**
+     * A fifo queue of events to be sent to the InputChannel. We can't send all InputMessages to
+     * the channel immediately when they are produced, because it's possible that the InputChannel
+     * is blocked (if the channel buffer is full). When that happens, we don't want to drop the
+     * events. Therefore, events should only be erased from the queue after they've been
+     * successfully written to the InputChannel.
+     */
+    std::queue<InputMessage> mOutboundQueue;
+    /**
+     * Try to send all of the events in mOutboundQueue over the InputChannel. Not all events might
+     * actually get sent, because it's possible that the channel is blocked.
+     */
+    void processOutboundEvents();
+
+    /**
+     * The time at which each event with the sequence number 'seq' was consumed.
+     * This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
+     * This collection is populated when the event is received, and the entries are erased when the
+     * events are finished. It should not grow infinitely because if an event is not ack'd, ANR
+     * will be raised for that connection, and no further events will be posted to that channel.
+     */
+    std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
+    /**
+     * Find and return the consumeTime associated with the provided sequence number. Crashes if
+     * the provided seq number is not found.
+     */
+    nsecs_t popConsumeTime(uint32_t seq);
+
+    // Event reading and processing
+    /**
+     * Read all of the available events from the InputChannel
+     */
+    std::vector<InputMessage> readAllMessages();
+
+    /**
+     * Send InputMessage to the corresponding InputConsumerCallbacks function.
+     * @param msg
+     */
+    void handleMessage(const InputMessage& msg) const;
+
+    // Batching
+    /**
+     * Batch messages that can be batched. When an unbatchable message is encountered, send it
+     * to the InputConsumerCallbacks immediately. If there are batches remaining,
+     * notify InputConsumerCallbacks.
+     */
+    void handleMessages(std::vector<InputMessage>&& messages);
+    /**
+     * Batched InputMessages, per deviceId.
+     * For each device, we are storing a queue of batched messages. These will all be collapsed into
+     * a single MotionEvent (up to a specific frameTime) when the consumer calls
+     * `consumeBatchedInputEvents`.
+     */
+    std::map<DeviceId, std::queue<InputMessage>> mBatches;
+    /**
+     * A map from a single sequence number to several sequence numbers. This is needed because of
+     * batching. When batching is enabled, a single MotionEvent will contain several samples. Each
+     * sample came from an individual InputMessage of Type::Motion, and therefore will have to be
+     * finished individually. Therefore, when the app calls "finish" on a (possibly batched)
+     * MotionEvent, we will need to check this map in case there are multiple sequence numbers
+     * associated with a single number that the app provided.
+     *
+     * For example:
+     * Suppose we received 4 InputMessage's of type Motion, with action MOVE:
+     * InputMessage(MOVE)   InputMessage(MOVE)   InputMessage(MOVE)   InputMessage(MOVE)
+     *    seq=10               seq=11               seq=12               seq=13
+     * The app consumed them all as a batch, which means that the app received a single MotionEvent
+     * with historySize=3 and seq = 10.
+     *
+     * This map will look like:
+     * {
+     *   10: [11, 12, 13],
+     * }
+     * So the sequence number 10 will have 3 other sequence numbers associated with it.
+     * When the app calls 'finish' for seq=10, we need to call 'finish' 4 times total, for sequence
+     * numbers 10, 11, 12, 13. The app is not aware of the sequence numbers of each sample inside
+     * the batched MotionEvent that it received.
+     */
+    std::map<uint32_t, std::vector<uint32_t>> mBatchedSequenceNumbers;
+};
+
+} // namespace android
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 65e93a9..3278c23 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -205,6 +205,7 @@
         "AccelerationCurve.cpp",
         "Input.cpp",
         "InputConsumer.cpp",
+        "InputConsumerNoResampling.cpp",
         "InputDevice.cpp",
         "InputEventLabels.cpp",
         "InputTransport.cpp",
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
new file mode 100644
index 0000000..1462c90
--- /dev/null
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -0,0 +1,534 @@
+/**
+ * Copyright 2024 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 "InputTransport"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+#include <inttypes.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <cutils/properties.h>
+#include <ftl/enum.h>
+#include <utils/Trace.h>
+
+#include <com_android_input_flags.h>
+#include <input/InputConsumerNoResampling.h>
+#include <input/PrintTools.h>
+#include <input/TraceTools.h>
+
+namespace input_flags = com::android::input::flags;
+
+namespace android {
+
+namespace {
+
+/**
+ * Log debug messages relating to the consumer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
+ */
+const bool DEBUG_TRANSPORT_CONSUMER =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
+
+void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
+                     msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action,
+                     msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode,
+                     msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime,
+                     msg.body.key.eventTime);
+}
+
+void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
+}
+
+void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
+}
+
+void initializeDragEvent(DragEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
+                     msg.body.drag.isExiting);
+}
+
+void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) {
+    const uint32_t pointerCount = msg.body.motion.pointerCount;
+    std::vector<PointerProperties> pointerProperties;
+    pointerProperties.reserve(pointerCount);
+    std::vector<PointerCoords> pointerCoords;
+    pointerCoords.reserve(pointerCount);
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back(msg.body.motion.pointers[i].properties);
+        pointerCoords.push_back(msg.body.motion.pointers[i].coords);
+    }
+
+    ui::Transform transform;
+    transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx,
+                   msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1});
+    ui::Transform displayTransform;
+    displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
+                          msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
+                          0, 0, 1});
+    event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
+                     msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action,
+                     msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags,
+                     msg.body.motion.metaState, msg.body.motion.buttonState,
+                     msg.body.motion.classification, transform, msg.body.motion.xPrecision,
+                     msg.body.motion.yPrecision, msg.body.motion.xCursorPosition,
+                     msg.body.motion.yCursorPosition, displayTransform, msg.body.motion.downTime,
+                     msg.body.motion.eventTime, pointerCount, pointerProperties.data(),
+                     pointerCoords.data());
+}
+
+void addSample(MotionEvent& event, const InputMessage& msg) {
+    uint32_t pointerCount = msg.body.motion.pointerCount;
+    std::vector<PointerCoords> pointerCoords;
+    pointerCoords.reserve(pointerCount);
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerCoords.push_back(msg.body.motion.pointers[i].coords);
+    }
+
+    // TODO(b/329770983): figure out if it's safe to combine events with mismatching metaState
+    event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
+    event.addSample(msg.body.motion.eventTime, pointerCoords.data());
+}
+
+void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
+}
+
+std::string outboundMessageToString(const InputMessage& outboundMsg) {
+    switch (outboundMsg.header.type) {
+        case InputMessage::Type::FINISHED: {
+            return android::base::StringPrintf("  Finish: seq=%" PRIu32 " handled=%s",
+                                               outboundMsg.header.seq,
+                                               toString(outboundMsg.body.finished.handled));
+        }
+        case InputMessage::Type::TIMELINE: {
+            return android::base::
+                    StringPrintf("  Timeline: inputEventId=%" PRId32 " gpuCompletedTime=%" PRId64
+                                 ", presentTime=%" PRId64,
+                                 outboundMsg.body.timeline.eventId,
+                                 outboundMsg.body.timeline
+                                         .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+                                 outboundMsg.body.timeline
+                                         .graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+        }
+        default: {
+            LOG(FATAL) << "Outbound message must be FINISHED or TIMELINE, got "
+                       << ftl::enum_string(outboundMsg.header.type);
+            return "Unreachable";
+        }
+    }
+}
+
+InputMessage createFinishedMessage(uint32_t seq, bool handled, nsecs_t consumeTime) {
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::FINISHED;
+    msg.header.seq = seq;
+    msg.body.finished.handled = handled;
+    msg.body.finished.consumeTime = consumeTime;
+    return msg;
+}
+
+InputMessage createTimelineMessage(int32_t inputEventId, nsecs_t gpuCompletedTime,
+                                   nsecs_t presentTime) {
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::TIMELINE;
+    msg.header.seq = 0;
+    msg.body.timeline.eventId = inputEventId;
+    msg.body.timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime;
+    msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
+    return msg;
+}
+
+} // namespace
+
+using android::base::Result;
+using android::base::StringPrintf;
+
+// --- InputConsumerNoResampling ---
+
+InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
+                                                     sp<Looper> looper,
+                                                     InputConsumerCallbacks& callbacks)
+      : mChannel(channel), mLooper(looper), mCallbacks(callbacks), mFdEvents(0) {
+    LOG_ALWAYS_FATAL_IF(mLooper == nullptr);
+    mCallback = sp<LooperEventCallback>::make(
+            std::bind(&InputConsumerNoResampling::handleReceiveCallback, this,
+                      std::placeholders::_1));
+    // In the beginning, there are no pending outbounds events; we only care about receiving
+    // incoming data.
+    setFdEvents(ALOOPER_EVENT_INPUT);
+}
+
+InputConsumerNoResampling::~InputConsumerNoResampling() {
+    ensureCalledOnLooperThread(__func__);
+    consumeBatchedInputEvents(std::nullopt);
+    while (!mOutboundQueue.empty()) {
+        processOutboundEvents();
+        // This is our last chance to ack the events. If we don't ack them here, we will get an ANR,
+        // so keep trying to send the events as long as they are present in the queue.
+    }
+    setFdEvents(0);
+}
+
+int InputConsumerNoResampling::handleReceiveCallback(int events) {
+    // Allowed return values of this function as documented in LooperCallback::handleEvent
+    constexpr int REMOVE_CALLBACK = 0;
+    constexpr int KEEP_CALLBACK = 1;
+
+    if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+        // This error typically occurs when the publisher has closed the input channel
+        // as part of removing a window or finishing an IME session, in which case
+        // the consumer will soon be disposed as well.
+        if (DEBUG_TRANSPORT_CONSUMER) {
+            LOG(INFO) << "The channel was hung up or an error occurred: " << mChannel->getName();
+        }
+        return REMOVE_CALLBACK;
+    }
+
+    int handledEvents = 0;
+    if (events & ALOOPER_EVENT_INPUT) {
+        std::vector<InputMessage> messages = readAllMessages();
+        handleMessages(std::move(messages));
+        handledEvents |= ALOOPER_EVENT_INPUT;
+    }
+
+    if (events & ALOOPER_EVENT_OUTPUT) {
+        processOutboundEvents();
+        handledEvents |= ALOOPER_EVENT_OUTPUT;
+    }
+    if (handledEvents != events) {
+        LOG(FATAL) << "Mismatch: handledEvents=" << handledEvents << ", events=" << events;
+    }
+    return KEEP_CALLBACK;
+}
+
+void InputConsumerNoResampling::processOutboundEvents() {
+    while (!mOutboundQueue.empty()) {
+        const InputMessage& outboundMsg = mOutboundQueue.front();
+
+        const status_t result = mChannel->sendMessage(&outboundMsg);
+        if (result == OK) {
+            if (outboundMsg.header.type == InputMessage::Type::FINISHED) {
+                ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/outboundMsg.header.seq);
+            }
+            // Successful send. Erase the entry and keep trying to send more
+            mOutboundQueue.pop();
+            continue;
+        }
+
+        // Publisher is busy, try again later. Keep this entry (do not erase)
+        if (result == WOULD_BLOCK) {
+            setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
+            return; // try again later
+        }
+
+        // Some other error. Give up
+        LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName()
+                   << "'.  status=" << statusToString(result) << "(" << result << ")";
+    }
+
+    // The queue is now empty. Tell looper there's no more output to expect.
+    setFdEvents(ALOOPER_EVENT_INPUT);
+}
+
+void InputConsumerNoResampling::finishInputEvent(uint32_t seq, bool handled) {
+    ensureCalledOnLooperThread(__func__);
+    mOutboundQueue.push(createFinishedMessage(seq, handled, popConsumeTime(seq)));
+    // also produce finish events for all batches for this seq (if any)
+    const auto it = mBatchedSequenceNumbers.find(seq);
+    if (it != mBatchedSequenceNumbers.end()) {
+        for (uint32_t subSeq : it->second) {
+            mOutboundQueue.push(createFinishedMessage(subSeq, handled, popConsumeTime(subSeq)));
+        }
+        mBatchedSequenceNumbers.erase(it);
+    }
+    processOutboundEvents();
+}
+
+bool InputConsumerNoResampling::probablyHasInput() const {
+    // Ideally, this would only be allowed to run on the looper thread, and in production, it will.
+    // However, for testing, it's convenient to call this while the looper thread is blocked, so
+    // we do not call ensureCalledOnLooperThread here.
+    return (!mBatches.empty()) || mChannel->probablyHasInput();
+}
+
+void InputConsumerNoResampling::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime,
+                                               nsecs_t presentTime) {
+    ensureCalledOnLooperThread(__func__);
+    mOutboundQueue.push(createTimelineMessage(inputEventId, gpuCompletedTime, presentTime));
+    processOutboundEvents();
+}
+
+nsecs_t InputConsumerNoResampling::popConsumeTime(uint32_t seq) {
+    auto it = mConsumeTimes.find(seq);
+    // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
+    // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
+    LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
+                        seq);
+    nsecs_t consumeTime = it->second;
+    mConsumeTimes.erase(it);
+    return consumeTime;
+}
+
+void InputConsumerNoResampling::setFdEvents(int events) {
+    if (mFdEvents != events) {
+        mFdEvents = events;
+        if (events != 0) {
+            mLooper->addFd(mChannel->getFd(), 0, events, mCallback, nullptr);
+        } else {
+            mLooper->removeFd(mChannel->getFd());
+        }
+    }
+}
+
+void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messages) {
+    // TODO(b/297226446) : add resampling
+    for (const InputMessage& msg : messages) {
+        if (msg.header.type == InputMessage::Type::MOTION) {
+            const int32_t action = msg.body.motion.action;
+            const DeviceId deviceId = msg.body.motion.deviceId;
+            const int32_t source = msg.body.motion.source;
+            const bool batchableEvent = (action == AMOTION_EVENT_ACTION_MOVE ||
+                                         action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
+                    (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
+                     isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+            if (batchableEvent) {
+                // add it to batch
+                mBatches[deviceId].emplace(msg);
+            } else {
+                // consume all pending batches for this event immediately
+                // TODO(b/329776327): figure out if this could be smarter by limiting the
+                // consumption only to the current device.
+                consumeBatchedInputEvents(std::nullopt);
+                handleMessage(msg);
+            }
+        } else {
+            // Non-motion events shouldn't force the consumption of pending batched events
+            handleMessage(msg);
+        }
+    }
+    // At the end of this, if we still have pending batches, notify the receiver about it.
+
+    // We need to carefully notify the InputConsumerCallbacks about the pending batch. The receiver
+    // could choose to consume all events when notified about the batch. That means that the
+    // "mBatches" variable could change when 'InputConsumerCallbacks::onBatchedInputEventPending' is
+    // invoked. We also can't notify the InputConsumerCallbacks in a while loop until mBatches is
+    // empty, because the receiver could choose to not consume the batch immediately.
+    std::set<int32_t> pendingBatchSources;
+    for (const auto& [_, pendingMessages] : mBatches) {
+        // Assume that all messages for a given device has the same source.
+        pendingBatchSources.insert(pendingMessages.front().body.motion.source);
+    }
+    for (const int32_t source : pendingBatchSources) {
+        const bool sourceStillRemaining =
+                std::any_of(mBatches.begin(), mBatches.end(), [=](const auto& pair) {
+                    return pair.second.front().body.motion.source == source;
+                });
+        if (sourceStillRemaining) {
+            mCallbacks.onBatchedInputEventPending(source);
+        }
+    }
+}
+
+std::vector<InputMessage> InputConsumerNoResampling::readAllMessages() {
+    std::vector<InputMessage> messages;
+    while (true) {
+        InputMessage msg;
+        status_t result = mChannel->receiveMessage(&msg);
+        switch (result) {
+            case OK: {
+                const auto [_, inserted] =
+                        mConsumeTimes.emplace(msg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+                LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
+                                    msg.header.seq);
+
+                // Trace the event processing timeline - event was just read from the socket
+                // TODO(b/329777420): distinguish between multiple instances of InputConsumer
+                // in the same process.
+                ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/msg.header.seq);
+                messages.push_back(msg);
+                break;
+            }
+            case WOULD_BLOCK: {
+                return messages;
+            }
+            case DEAD_OBJECT: {
+                LOG(FATAL) << "Got a dead object for " << mChannel->getName();
+                break;
+            }
+            case BAD_VALUE: {
+                LOG(FATAL) << "Got a bad value for " << mChannel->getName();
+                break;
+            }
+            default: {
+                LOG(FATAL) << "Unexpected error: " << result;
+                break;
+            }
+        }
+    }
+}
+
+void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const {
+    switch (msg.header.type) {
+        case InputMessage::Type::KEY: {
+            KeyEvent keyEvent;
+            initializeKeyEvent(keyEvent, msg);
+            mCallbacks.onKeyEvent(std::move(keyEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::MOTION: {
+            MotionEvent motionEvent;
+            initializeMotionEvent(motionEvent, msg);
+            mCallbacks.onMotionEvent(std::move(motionEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::FINISHED:
+        case InputMessage::Type::TIMELINE: {
+            LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by "
+                             "InputConsumer on %s",
+                             ftl::enum_string(msg.header.type).c_str(),
+                             mChannel->getName().c_str());
+            break;
+        }
+
+        case InputMessage::Type::FOCUS: {
+            FocusEvent focusEvent;
+            initializeFocusEvent(focusEvent, msg);
+            mCallbacks.onFocusEvent(std::move(focusEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::CAPTURE: {
+            CaptureEvent captureEvent;
+            initializeCaptureEvent(captureEvent, msg);
+            mCallbacks.onCaptureEvent(std::move(captureEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::DRAG: {
+            DragEvent dragEvent;
+            initializeDragEvent(dragEvent, msg);
+            mCallbacks.onDragEvent(std::move(dragEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::TOUCH_MODE: {
+            TouchModeEvent touchModeEvent;
+            initializeTouchModeEvent(touchModeEvent, msg);
+            mCallbacks.onTouchModeEvent(std::move(touchModeEvent), msg.header.seq);
+            break;
+        }
+    }
+}
+
+bool InputConsumerNoResampling::consumeBatchedInputEvents(
+        std::optional<nsecs_t> requestedFrameTime) {
+    ensureCalledOnLooperThread(__func__);
+    // When batching is not enabled, we want to consume all events. That's equivalent to having an
+    // infinite frameTime.
+    const nsecs_t frameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
+    bool producedEvents = false;
+    for (auto& [deviceId, messages] : mBatches) {
+        MotionEvent motion;
+        std::optional<uint32_t> firstSeqForBatch;
+        std::vector<uint32_t> sequences;
+        while (!messages.empty()) {
+            const InputMessage& msg = messages.front();
+            if (msg.body.motion.eventTime > frameTime) {
+                break;
+            }
+            if (!firstSeqForBatch.has_value()) {
+                initializeMotionEvent(motion, msg);
+                firstSeqForBatch = msg.header.seq;
+                const auto [_, inserted] = mBatchedSequenceNumbers.insert({*firstSeqForBatch, {}});
+                if (!inserted) {
+                    LOG(FATAL) << "The sequence " << msg.header.seq << " was already present!";
+                }
+            } else {
+                addSample(motion, msg);
+                mBatchedSequenceNumbers[*firstSeqForBatch].push_back(msg.header.seq);
+            }
+            messages.pop();
+        }
+        if (firstSeqForBatch.has_value()) {
+            mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch);
+            producedEvents = true;
+        } else {
+            // This is OK, it just means that the frameTime is too old (all events that we have
+            // pending are in the future of the frametime). Maybe print a
+            // warning? If there are multiple devices active though, this might be normal and can
+            // just be ignored, unless none of them resulted in any consumption (in that case, this
+            // function would already return "false" so we could just leave it up to the caller).
+        }
+    }
+    std::erase_if(mBatches, [](const auto& pair) { return pair.second.empty(); });
+    return producedEvents;
+}
+
+void InputConsumerNoResampling::ensureCalledOnLooperThread(const char* func) const {
+    sp<Looper> callingThreadLooper = Looper::getForThread();
+    if (callingThreadLooper != mLooper) {
+        LOG(FATAL) << "The function " << func << " can only be called on the looper thread";
+    }
+}
+
+std::string InputConsumerNoResampling::dump() const {
+    ensureCalledOnLooperThread(__func__);
+    std::string out;
+    if (mOutboundQueue.empty()) {
+        out += "mOutboundQueue: <empty>\n";
+    } else {
+        out += "mOutboundQueue:\n";
+        // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
+        // doesn't provide a good way to iterate over the entire container.
+        std::queue<InputMessage> tmpQueue = mOutboundQueue;
+        while (!tmpQueue.empty()) {
+            out += std::string("  ") + outboundMessageToString(tmpQueue.front()) + "\n";
+            tmpQueue.pop();
+        }
+    }
+
+    if (mBatches.empty()) {
+        out += "mBatches: <empty>\n";
+    } else {
+        out += "mBatches:\n";
+        for (const auto& [deviceId, messages] : mBatches) {
+            out += "  Device id ";
+            out += std::to_string(deviceId);
+            out += ":\n";
+            // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
+            // doesn't provide a good way to iterate over the entire container.
+            std::queue<InputMessage> tmpQueue = messages;
+            while (!tmpQueue.empty()) {
+                LOG_ALWAYS_FATAL_IF(tmpQueue.front().header.type != InputMessage::Type::MOTION);
+                MotionEvent motion;
+                initializeMotionEvent(motion, tmpQueue.front());
+                out += std::string("    ") + streamableToString(motion) + "\n";
+                tmpQueue.pop();
+            }
+        }
+    }
+
+    return out;
+}
+
+} // namespace android
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 93af4c2..e67a65a 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -19,6 +19,7 @@
         "InputDevice_test.cpp",
         "InputEvent_test.cpp",
         "InputPublisherAndConsumer_test.cpp",
+        "InputPublisherAndConsumerNoResampling_test.cpp",
         "InputVerifier_test.cpp",
         "MotionPredictor_test.cpp",
         "MotionPredictorMetricsManager_test.cpp",
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
new file mode 100644
index 0000000..e24ae49
--- /dev/null
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -0,0 +1,807 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#include <android-base/logging.h>
+#include <attestation/HmacKeyManager.h>
+#include <ftl/enum.h>
+#include <gtest/gtest.h>
+#include <gui/constants.h>
+#include <input/BlockingQueue.h>
+#include <input/InputConsumerNoResampling.h>
+#include <input/InputTransport.h>
+
+using android::base::Result;
+
+namespace android {
+
+namespace {
+
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+static constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static auto constexpr TIMEOUT = 5s;
+
+struct Pointer {
+    int32_t id;
+    float x;
+    float y;
+    bool isResampled = false;
+};
+
+// A collection of arguments to be sent as publishMotionEvent(). The saved members of this struct
+// allow to check the expectations against the event acquired from the InputConsumerCallbacks. To
+// help simplify expectation checking it carries members not present in MotionEvent, like
+// |rawXScale|.
+struct PublishMotionArgs {
+    const int32_t action;
+    const nsecs_t downTime;
+    const uint32_t seq;
+    const int32_t eventId;
+    const int32_t deviceId = 1;
+    const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
+    const int32_t displayId = ADISPLAY_ID_DEFAULT;
+    const int32_t actionButton = 0;
+    const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
+    const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+    const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
+    const MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE;
+    const float xScale = 2;
+    const float yScale = 3;
+    const float xOffset = -10;
+    const float yOffset = -20;
+    const float rawXScale = 4;
+    const float rawYScale = -5;
+    const float rawXOffset = -11;
+    const float rawYOffset = 42;
+    const float xPrecision = 0.25;
+    const float yPrecision = 0.5;
+    const float xCursorPosition = 1.3;
+    const float yCursorPosition = 50.6;
+    std::array<uint8_t, 32> hmac;
+    int32_t flags;
+    ui::Transform transform;
+    ui::Transform rawTransform;
+    const nsecs_t eventTime;
+    size_t pointerCount;
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+
+    PublishMotionArgs(int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers,
+                      const uint32_t seq);
+};
+
+PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime,
+                                     const std::vector<Pointer>& pointers, const uint32_t inSeq)
+      : action(inAction),
+        downTime(inDownTime),
+        seq(inSeq),
+        eventId(InputEvent::nextId()),
+        eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) {
+    hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
+            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
+
+    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    pointerCount = pointers.size();
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back({});
+        pointerProperties[i].clear();
+        pointerProperties[i].id = pointers[i].id;
+        pointerProperties[i].toolType = ToolType::FINGER;
+
+        pointerCoords.push_back({});
+        pointerCoords[i].clear();
+        pointerCoords[i].isResampled = pointers[i].isResampled;
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
+    }
+    transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
+    rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+}
+
+// Checks expectations against |motionEvent| acquired from an InputConsumer. Floating point
+// comparisons limit precision to EPSILON.
+void verifyArgsEqualToEvent(const PublishMotionArgs& args, const MotionEvent& motionEvent) {
+    EXPECT_EQ(args.eventId, motionEvent.getId());
+    EXPECT_EQ(args.deviceId, motionEvent.getDeviceId());
+    EXPECT_EQ(args.source, motionEvent.getSource());
+    EXPECT_EQ(args.displayId, motionEvent.getDisplayId());
+    EXPECT_EQ(args.hmac, motionEvent.getHmac());
+    EXPECT_EQ(args.action, motionEvent.getAction());
+    EXPECT_EQ(args.downTime, motionEvent.getDownTime());
+    EXPECT_EQ(args.flags, motionEvent.getFlags());
+    EXPECT_EQ(args.edgeFlags, motionEvent.getEdgeFlags());
+    EXPECT_EQ(args.metaState, motionEvent.getMetaState());
+    EXPECT_EQ(args.buttonState, motionEvent.getButtonState());
+    EXPECT_EQ(args.classification, motionEvent.getClassification());
+    EXPECT_EQ(args.transform, motionEvent.getTransform());
+    EXPECT_NEAR((-args.rawXOffset / args.rawXScale) * args.xScale + args.xOffset,
+                motionEvent.getRawXOffset(), EPSILON);
+    EXPECT_NEAR((-args.rawYOffset / args.rawYScale) * args.yScale + args.yOffset,
+                motionEvent.getRawYOffset(), EPSILON);
+    EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision());
+    EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision());
+    EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.yCursorPosition, motionEvent.getRawYCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.xCursorPosition * args.xScale + args.xOffset, motionEvent.getXCursorPosition(),
+                EPSILON);
+    EXPECT_NEAR(args.yCursorPosition * args.yScale + args.yOffset, motionEvent.getYCursorPosition(),
+                EPSILON);
+    EXPECT_EQ(args.rawTransform, motionEvent.getRawTransform());
+    EXPECT_EQ(args.eventTime, motionEvent.getEventTime());
+    EXPECT_EQ(args.pointerCount, motionEvent.getPointerCount());
+    EXPECT_EQ(0U, motionEvent.getHistorySize());
+
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(args.pointerProperties[i].id, motionEvent.getPointerId(i));
+        EXPECT_EQ(args.pointerProperties[i].toolType, motionEvent.getToolType(i));
+
+        const auto& pc = args.pointerCoords[i];
+        EXPECT_EQ(pc, motionEvent.getSamplePointerCoords()[i]);
+
+        EXPECT_NEAR(pc.getX() * args.rawXScale + args.rawXOffset, motionEvent.getRawX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.rawYScale + args.rawYOffset, motionEvent.getRawY(i), EPSILON);
+        EXPECT_NEAR(pc.getX() * args.xScale + args.xOffset, motionEvent.getX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.yScale + args.yOffset, motionEvent.getY(i), EPSILON);
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent.getPressure(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent.getSize(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent.getTouchMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent.getTouchMinor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent.getToolMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent.getToolMinor(i));
+
+        // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is
+        // "up", and the positive y direction is "down".
+        const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+        const float x = sinf(unscaledOrientation) * args.xScale;
+        const float y = -cosf(unscaledOrientation) * args.yScale;
+        EXPECT_EQ(atan2f(x, -y), motionEvent.getOrientation(i));
+    }
+}
+
+void publishMotionEvent(InputPublisher& publisher, const PublishMotionArgs& a) {
+    status_t status =
+            publisher.publishMotionEvent(a.seq, a.eventId, a.deviceId, a.source, a.displayId,
+                                         a.hmac, a.action, a.actionButton, a.flags, a.edgeFlags,
+                                         a.metaState, a.buttonState, a.classification, a.transform,
+                                         a.xPrecision, a.yPrecision, a.xCursorPosition,
+                                         a.yCursorPosition, a.rawTransform, a.downTime, a.eventTime,
+                                         a.pointerCount, a.pointerProperties.data(),
+                                         a.pointerCoords.data());
+    ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
+}
+
+Result<InputPublisher::ConsumerResponse> receiveConsumerResponse(
+        InputPublisher& publisher, std::chrono::milliseconds timeout) {
+    const std::chrono::time_point start = std::chrono::steady_clock::now();
+
+    while (true) {
+        Result<InputPublisher::ConsumerResponse> result = publisher.receiveConsumerResponse();
+        if (result.ok()) {
+            return result;
+        }
+        const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
+        if (waited > timeout) {
+            return result;
+        }
+    }
+}
+
+void verifyFinishedSignal(InputPublisher& publisher, uint32_t seq, nsecs_t publishTime) {
+    Result<InputPublisher::ConsumerResponse> result = receiveConsumerResponse(publisher, TIMEOUT);
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse returned " << result.error().message();
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_TRUE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
+            << "finished signal's consume time should be greater than publish time";
+}
+
+} // namespace
+
+class InputConsumerMessageHandler : public MessageHandler {
+public:
+    InputConsumerMessageHandler(std::function<void(const Message&)> function)
+          : mFunction(function) {}
+
+private:
+    void handleMessage(const Message& message) override { mFunction(message); }
+
+    std::function<void(const Message&)> mFunction;
+};
+
+class InputPublisherAndConsumerNoResamplingTest : public testing::Test,
+                                                  public InputConsumerCallbacks {
+protected:
+    std::unique_ptr<InputChannel> mClientChannel;
+    std::unique_ptr<InputPublisher> mPublisher;
+    std::unique_ptr<InputConsumerNoResampling> mConsumer;
+
+    std::thread mLooperThread;
+    sp<Looper> mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
+
+    // LOOPER CONTROL
+    // Set to false when you want the looper to exit
+    std::atomic<bool> mExitLooper = false;
+    std::mutex mLock;
+
+    // Used by test to notify looper that the value of "mLooperMayProceed" has changed
+    std::condition_variable mNotifyLooperMayProceed;
+    bool mLooperMayProceed GUARDED_BY(mLock){true};
+    // Used by looper to notify the test that it's about to block on "mLooperMayProceed" -> true
+    std::condition_variable mNotifyLooperWaiting;
+    bool mLooperIsBlocked GUARDED_BY(mLock){false};
+
+    std::condition_variable mNotifyConsumerDestroyed;
+    bool mConsumerDestroyed GUARDED_BY(mLock){false};
+
+    void runLooper() {
+        static constexpr int LOOP_INDEFINITELY = -1;
+        Looper::setForThread(mLooper);
+        // Loop forever -- this thread is dedicated to servicing the looper callbacks.
+        while (!mExitLooper) {
+            mLooper->pollOnce(/*timeoutMillis=*/LOOP_INDEFINITELY);
+        }
+    }
+
+    void SetUp() override {
+        std::unique_ptr<InputChannel> serverChannel;
+        status_t result =
+                InputChannel::openInputChannelPair("channel name", serverChannel, mClientChannel);
+        ASSERT_EQ(OK, result);
+
+        mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
+        mMessageHandler = sp<InputConsumerMessageHandler>::make(
+                [this](const Message& message) { handleMessage(message); });
+        mLooperThread = std::thread([this] { runLooper(); });
+        sendMessage(LooperMessage::CREATE_CONSUMER);
+    }
+
+    void publishAndConsumeKeyEvent();
+    void publishAndConsumeMotionStream();
+    void publishAndConsumeMotionDown(nsecs_t downTime);
+    void publishAndConsumeBatchedMotionMove(nsecs_t downTime);
+    void publishAndConsumeFocusEvent();
+    void publishAndConsumeCaptureEvent();
+    void publishAndConsumeDragEvent();
+    void publishAndConsumeTouchModeEvent();
+    void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
+                                      const std::vector<Pointer>& pointers);
+    void TearDown() override {
+        // Destroy the consumer, flushing any of the pending ack's.
+        sendMessage(LooperMessage::DESTROY_CONSUMER);
+        {
+            std::unique_lock lock(mLock);
+            base::ScopedLockAssertion assumeLocked(mLock);
+            mNotifyConsumerDestroyed.wait(lock, [this] { return mConsumerDestroyed; });
+        }
+        // Stop the looper thread so that we can destroy the object.
+        mExitLooper = true;
+        mLooper->wake();
+        mLooperThread.join();
+    }
+
+protected:
+    // Interaction with the looper thread
+    enum class LooperMessage : int {
+        CALL_PROBABLY_HAS_INPUT,
+        CREATE_CONSUMER,
+        DESTROY_CONSUMER,
+        CALL_REPORT_TIMELINE,
+        BLOCK_LOOPER,
+    };
+    void sendMessage(LooperMessage message);
+    struct ReportTimelineArgs {
+        int32_t inputEventId;
+        nsecs_t gpuCompletedTime;
+        nsecs_t presentTime;
+    };
+    // The input to the function "InputConsumer::reportTimeline". Populated on the test thread and
+    // accessed on the looper thread.
+    BlockingQueue<ReportTimelineArgs> mReportTimelineArgs;
+    // The output of calling "InputConsumer::probablyHasInput()". Populated on the looper thread and
+    // accessed on the test thread.
+    BlockingQueue<bool> mProbablyHasInputResponses;
+
+private:
+    sp<MessageHandler> mMessageHandler;
+    void handleMessage(const Message& message);
+
+    static auto constexpr NO_EVENT_TIMEOUT = 10ms;
+    // The sequence number to use when publishing the next event
+    uint32_t mSeq = 1;
+
+    BlockingQueue<KeyEvent> mKeyEvents;
+    BlockingQueue<MotionEvent> mMotionEvents;
+    BlockingQueue<FocusEvent> mFocusEvents;
+    BlockingQueue<CaptureEvent> mCaptureEvents;
+    BlockingQueue<DragEvent> mDragEvents;
+    BlockingQueue<TouchModeEvent> mTouchModeEvents;
+
+    // InputConsumerCallbacks interface
+    void onKeyEvent(KeyEvent&& event, uint32_t seq) override {
+        mKeyEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onMotionEvent(MotionEvent&& event, uint32_t seq) override {
+        mMotionEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onBatchedInputEventPending(int32_t pendingBatchSource) override {
+        if (!mConsumer->probablyHasInput()) {
+            ADD_FAILURE() << "should deterministically have input because there is a batch";
+        }
+        mConsumer->consumeBatchedInputEvents(std::nullopt);
+    };
+    void onFocusEvent(FocusEvent&& event, uint32_t seq) override {
+        mFocusEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+    void onCaptureEvent(CaptureEvent&& event, uint32_t seq) override {
+        mCaptureEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+    void onDragEvent(DragEvent&& event, uint32_t seq) override {
+        mDragEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onTouchModeEvent(TouchModeEvent&& event, uint32_t seq) override {
+        mTouchModeEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+};
+
+void InputPublisherAndConsumerNoResamplingTest::sendMessage(LooperMessage message) {
+    Message msg{ftl::to_underlying(message)};
+    mLooper->sendMessage(mMessageHandler, msg);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::handleMessage(const Message& message) {
+    switch (static_cast<LooperMessage>(message.what)) {
+        case LooperMessage::CALL_PROBABLY_HAS_INPUT: {
+            mProbablyHasInputResponses.push(mConsumer->probablyHasInput());
+            break;
+        }
+        case LooperMessage::CREATE_CONSUMER: {
+            mConsumer = std::make_unique<InputConsumerNoResampling>(std::move(mClientChannel),
+                                                                    mLooper, *this);
+            break;
+        }
+        case LooperMessage::DESTROY_CONSUMER: {
+            mConsumer = nullptr;
+            {
+                std::unique_lock lock(mLock);
+                mConsumerDestroyed = true;
+            }
+            mNotifyConsumerDestroyed.notify_all();
+            break;
+        }
+        case LooperMessage::CALL_REPORT_TIMELINE: {
+            std::optional<ReportTimelineArgs> args = mReportTimelineArgs.pop();
+            if (!args.has_value()) {
+                ADD_FAILURE() << "Couldn't get the 'reportTimeline' args in time";
+                return;
+            }
+            mConsumer->reportTimeline(args->inputEventId, args->gpuCompletedTime,
+                                      args->presentTime);
+            break;
+        }
+        case LooperMessage::BLOCK_LOOPER: {
+            {
+                std::unique_lock lock(mLock);
+                mLooperIsBlocked = true;
+            }
+            mNotifyLooperWaiting.notify_all();
+
+            {
+                std::unique_lock lock(mLock);
+                base::ScopedLockAssertion assumeLocked(mLock);
+                mNotifyLooperMayProceed.wait(lock, [this] { return mLooperMayProceed; });
+            }
+
+            {
+                std::unique_lock lock(mLock);
+                mLooperIsBlocked = false;
+            }
+            mNotifyLooperWaiting.notify_all();
+            break;
+        }
+    }
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeKeyEvent() {
+    status_t status;
+
+    const uint32_t seq = mSeq++;
+    int32_t eventId = InputEvent::nextId();
+    constexpr int32_t deviceId = 1;
+    constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
+    constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
+    constexpr std::array<uint8_t, 32> hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21,
+                                              20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10,
+                                              9,  8,  7,  6,  5,  4,  3,  2,  1,  0};
+    constexpr int32_t action = AKEY_EVENT_ACTION_DOWN;
+    constexpr int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM;
+    constexpr int32_t keyCode = AKEYCODE_ENTER;
+    constexpr int32_t scanCode = 13;
+    constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+    constexpr int32_t repeatCount = 1;
+    constexpr nsecs_t downTime = 3;
+    constexpr nsecs_t eventTime = 4;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishKeyEvent(seq, eventId, deviceId, source, displayId, hmac, action,
+                                         flags, keyCode, scanCode, metaState, repeatCount, downTime,
+                                         eventTime);
+    ASSERT_EQ(OK, status) << "publisher publishKeyEvent should return OK";
+
+    std::optional<KeyEvent> keyEvent = mKeyEvents.popWithTimeout(TIMEOUT);
+
+    sendMessage(LooperMessage::CALL_PROBABLY_HAS_INPUT);
+    std::optional<bool> probablyHasInput = mProbablyHasInputResponses.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(probablyHasInput.has_value());
+    ASSERT_FALSE(probablyHasInput.value()) << "no events should be waiting after being consumed";
+
+    ASSERT_TRUE(keyEvent.has_value()) << "consumer should have returned non-NULL event";
+
+    EXPECT_EQ(eventId, keyEvent->getId());
+    EXPECT_EQ(deviceId, keyEvent->getDeviceId());
+    EXPECT_EQ(source, keyEvent->getSource());
+    EXPECT_EQ(displayId, keyEvent->getDisplayId());
+    EXPECT_EQ(hmac, keyEvent->getHmac());
+    EXPECT_EQ(action, keyEvent->getAction());
+    EXPECT_EQ(flags, keyEvent->getFlags());
+    EXPECT_EQ(keyCode, keyEvent->getKeyCode());
+    EXPECT_EQ(scanCode, keyEvent->getScanCode());
+    EXPECT_EQ(metaState, keyEvent->getMetaState());
+    EXPECT_EQ(repeatCount, keyEvent->getRepeatCount());
+    EXPECT_EQ(downTime, keyEvent->getDownTime());
+    EXPECT_EQ(eventTime, keyEvent->getEventTime());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionStream() {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionDown(nsecs_t downTime) {
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMove(
+        nsecs_t downTime) {
+    uint32_t seq = mSeq++;
+    const std::vector<Pointer> pointers = {Pointer{.id = 0, .x = 20, .y = 30}};
+    PublishMotionArgs args(AMOTION_EVENT_ACTION_MOVE, downTime, pointers, seq);
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    // Block the looper thread, preventing it from being able to service any of the fd callbacks.
+
+    {
+        std::scoped_lock lock(mLock);
+        mLooperMayProceed = false;
+    }
+    sendMessage(LooperMessage::BLOCK_LOOPER);
+    {
+        std::unique_lock lock(mLock);
+        mNotifyLooperWaiting.wait(lock, [this] { return mLooperIsBlocked; });
+    }
+
+    publishMotionEvent(*mPublisher, args);
+
+    // Ensure no event arrives because the UI thread is blocked
+    std::optional<MotionEvent> noEvent = mMotionEvents.popWithTimeout(NO_EVENT_TIMEOUT);
+    ASSERT_FALSE(noEvent.has_value()) << "Got unexpected event: " << *noEvent;
+
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_FALSE(result.ok());
+    ASSERT_EQ(WOULD_BLOCK, result.error().code());
+
+    // We shouldn't be calling mConsumer on the UI thread, but in this situation, the looper
+    // thread is locked, so this should be safe to do.
+    ASSERT_TRUE(mConsumer->probablyHasInput())
+            << "should deterministically have input because there is a batch";
+
+    // Now, unblock the looper thread, so that the event can arrive.
+    {
+        std::scoped_lock lock(mLock);
+        mLooperMayProceed = true;
+    }
+    mNotifyLooperMayProceed.notify_all();
+
+    std::optional<MotionEvent> motion = mMotionEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(motion.has_value());
+    ASSERT_EQ(ACTION_MOVE, motion->getAction());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionEvent(
+        int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
+    uint32_t seq = mSeq++;
+    PublishMotionArgs args(action, downTime, pointers, seq);
+    nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    publishMotionEvent(*mPublisher, args);
+
+    std::optional<MotionEvent> event = mMotionEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event";
+
+    verifyArgsEqualToEvent(args, *event);
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeFocusEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool hasFocus = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishFocusEvent(seq, eventId, hasFocus);
+    ASSERT_EQ(OK, status) << "publisher publishFocusEvent should return OK";
+
+    std::optional<FocusEvent> focusEvent = mFocusEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(focusEvent.has_value()) << "consumer should have returned non-NULL event";
+    EXPECT_EQ(eventId, focusEvent->getId());
+    EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeCaptureEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 42;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool captureEnabled = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishCaptureEvent(seq, eventId, captureEnabled);
+    ASSERT_EQ(OK, status) << "publisher publishCaptureEvent should return OK";
+
+    std::optional<CaptureEvent> event = mCaptureEvents.popWithTimeout(TIMEOUT);
+
+    ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event";
+
+    const CaptureEvent& captureEvent = *event;
+    EXPECT_EQ(eventId, captureEvent.getId());
+    EXPECT_EQ(captureEnabled, captureEvent.getPointerCaptureEnabled());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeDragEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool isExiting = false;
+    constexpr float x = 10;
+    constexpr float y = 15;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishDragEvent(seq, eventId, x, y, isExiting);
+    ASSERT_EQ(OK, status) << "publisher publishDragEvent should return OK";
+
+    std::optional<DragEvent> event = mDragEvents.popWithTimeout(TIMEOUT);
+
+    ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event";
+
+    const DragEvent& dragEvent = *event;
+    EXPECT_EQ(eventId, dragEvent.getId());
+    EXPECT_EQ(isExiting, dragEvent.isExiting());
+    EXPECT_EQ(x, dragEvent.getX());
+    EXPECT_EQ(y, dragEvent.getY());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeTouchModeEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool touchModeEnabled = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishTouchModeEvent(seq, eventId, touchModeEnabled);
+    ASSERT_EQ(OK, status) << "publisher publishTouchModeEvent should return OK";
+
+    std::optional<TouchModeEvent> event = mTouchModeEvents.popWithTimeout(TIMEOUT);
+    ASSERT_NE(std::nullopt, event);
+
+    const TouchModeEvent& touchModeEvent = *event;
+    EXPECT_EQ(eventId, touchModeEvent.getId());
+    EXPECT_EQ(touchModeEnabled, touchModeEvent.isInTouchMode());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, SendTimeline) {
+    const int32_t inputEventId = 20;
+    const nsecs_t gpuCompletedTime = 30;
+    const nsecs_t presentTime = 40;
+
+    mReportTimelineArgs.emplace(inputEventId, gpuCompletedTime, presentTime);
+    sendMessage(LooperMessage::CALL_REPORT_TIMELINE);
+
+    Result<InputPublisher::ConsumerResponse> result = receiveConsumerResponse(*mPublisher, TIMEOUT);
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Timeline>(*result));
+    const InputPublisher::Timeline& timeline = std::get<InputPublisher::Timeline>(*result);
+    ASSERT_EQ(inputEventId, timeline.inputEventId);
+    ASSERT_EQ(gpuCompletedTime, timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]);
+    ASSERT_EQ(presentTime, timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishKeyEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionMoveEvent_EndToEnd) {
+    // Publish a DOWN event before MOVE to pass the InputVerifier checks.
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionDown(downTime));
+
+    // Publish the MOVE event and check expectations.
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeBatchedMotionMove(downTime));
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishFocusEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishCaptureEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishDragEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishTouchModeEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+       PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
+    status_t status;
+    const size_t pointerCount = 1;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].clear();
+        pointerCoords[i].clear();
+    }
+
+    ui::Transform identityTransform;
+    status =
+            mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
+                                           0, 0, 0, MotionClassification::NONE, identityTransform,
+                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+       PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) {
+    status_t status;
+    const size_t pointerCount = 0;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    ui::Transform identityTransform;
+    status =
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
+                                           0, 0, 0, MotionClassification::NONE, identityTransform,
+                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+       PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
+    status_t status;
+    const size_t pointerCount = MAX_POINTERS + 1;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].clear();
+        pointerCoords[i].clear();
+    }
+
+    ui::Transform identityTransform;
+    status =
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
+                                           0, 0, 0, MotionClassification::NONE, identityTransform,
+                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMultipleEvents_EndToEnd) {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
+}
+
+} // namespace android
diff --git a/libs/tracing_perfetto/TEST_MAPPING b/libs/tracing_perfetto/TEST_MAPPING
new file mode 100644
index 0000000..1805e18
--- /dev/null
+++ b/libs/tracing_perfetto/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "libtracing_perfetto_tests"
+    }
+  ]
+}
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index e487cbc..af0bcff 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -67,7 +67,6 @@
 static const char* RO_DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
 static const char* RO_BOARD_PLATFORM_PROPERTY = "ro.board.platform";
 static const char* ANGLE_SUFFIX_VALUE = "angle";
-static const char* VENDOR_ANGLE_BUILD = "ro.gfx.angle.supported";
 
 static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = {
         PERSIST_DRIVER_SUFFIX_PROPERTY,
@@ -494,14 +493,9 @@
 
     void* dso = nullptr;
 
-    const bool AngleInVendor = property_get_bool(VENDOR_ANGLE_BUILD, false);
     const bool isSuffixAngle = suffix != nullptr && strcmp(suffix, ANGLE_SUFFIX_VALUE) == 0;
-    // Only use sphal namespace when system ANGLE binaries are not the default drivers.
-    const bool useSphalNamespace =  !isSuffixAngle || AngleInVendor;
-
     const std::string absolutePath =
-            findLibrary(libraryName, useSphalNamespace ? VENDOR_LIB_EGL_DIR : SYSTEM_LIB_PATH,
-                        exact);
+            findLibrary(libraryName, isSuffixAngle ? SYSTEM_LIB_PATH : VENDOR_LIB_EGL_DIR, exact);
     if (absolutePath.empty()) {
         // this happens often, we don't want to log an error
         return nullptr;
@@ -509,8 +503,9 @@
     const char* const driverAbsolutePath = absolutePath.c_str();
 
     // Currently the default driver is unlikely to be ANGLE on most devices,
-    // hence put this first.
-    if (useSphalNamespace) {
+    // hence put this first. Only use sphal namespace when system ANGLE binaries
+    // are not the default drivers.
+    if (!isSuffixAngle) {
         // Try to load drivers from the 'sphal' namespace, if it exist. Fall back to
         // the original routine when the namespace does not exist.
         // See /system/linkerconfig/contents/namespace for the configuration of the
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index ad59f1a..56c29e2 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -506,6 +506,8 @@
     }
 
     int noVoteLayers = 0;
+    // Layers that prefer the same mode ("no-op").
+    int noPreferenceLayers = 0;
     int minVoteLayers = 0;
     int maxVoteLayers = 0;
     int explicitDefaultVoteLayers = 0;
@@ -549,10 +551,7 @@
                     explicitCategoryVoteLayers++;
                 }
                 if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
-                    // Count this layer for Min vote as well. The explicit vote avoids
-                    // touch boost and idle for choosing a category, while Min vote is for correct
-                    // behavior when all layers are Min or no vote.
-                    minVoteLayers++;
+                    noPreferenceLayers++;
                 }
                 break;
             case LayerVoteType::Heuristic:
@@ -612,6 +611,16 @@
         return {ranking, kNoSignals};
     }
 
+    // If all layers are category NoPreference, use the current config.
+    if (noPreferenceLayers + noVoteLayers == layers.size()) {
+        ALOGV("All layers NoPreference");
+        const auto ascendingWithPreferred =
+                rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
+        ATRACE_FORMAT_INSTANT("%s (All layers NoPreference)",
+                              to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
+        return {ascendingWithPreferred, kNoSignals};
+    }
+
     const bool smoothSwitchOnly = categorySmoothSwitchOnlyLayers > 0;
     const DisplayModeId activeModeId = activeMode.getId();
 
@@ -643,6 +652,7 @@
               ftl::enum_string(layer.frameRateCategory).c_str());
         if (layer.isNoVote() || layer.frameRateCategory == FrameRateCategory::NoPreference ||
             layer.vote == LayerVoteType::Min) {
+            ALOGV("%s scoring skipped due to vote", formatLayerInfo(layer, layer.weight).c_str());
             continue;
         }
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index fe0e3d1..a155f5d 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -295,6 +295,12 @@
                     << "Did not get expected frame rate for frameRate="
                     << to_string(testCase.desiredFrameRate)
                     << " category=" << ftl::enum_string(testCase.frameRateCategory);
+            EXPECT_EQ(testCase.expectedModeId,
+                      selector.getBestFrameRateMode(layers).modePtr->getId())
+                    << "Did not get expected DisplayModeId for modeId="
+                    << ftl::to_underlying(testCase.expectedModeId)
+                    << " frameRate=" << to_string(testCase.desiredFrameRate)
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
         }
     }
 };
@@ -1534,7 +1540,7 @@
             {0_Hz, FrameRateCategory::High, 90_Hz},
             {0_Hz, FrameRateCategory::Normal, 60_Hz},
             {0_Hz, FrameRateCategory::Low, 30_Hz},
-            {0_Hz, FrameRateCategory::NoPreference, 30_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
 
             // Cases that have both desired frame rate and frame rate category requirements.
             {24_Hz, FrameRateCategory::High, 120_Hz},
@@ -1591,6 +1597,7 @@
 
         // Expected result
         Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId90;
     };
 
     testFrameRateCategoryWithMultipleLayers(
@@ -1605,7 +1612,7 @@
 
     testFrameRateCategoryWithMultipleLayers(
             std::initializer_list<Case>{
-                    {0_Hz, FrameRateCategory::Normal, 60_Hz},
+                    {0_Hz, FrameRateCategory::Normal, 60_Hz, kModeId60},
                     {0_Hz, FrameRateCategory::High, 90_Hz},
                     {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
             },
@@ -1614,18 +1621,18 @@
     testFrameRateCategoryWithMultipleLayers(
             std::initializer_list<Case>{
                     {30_Hz, FrameRateCategory::High, 90_Hz},
-                    {24_Hz, FrameRateCategory::High, 120_Hz},
-                    {12_Hz, FrameRateCategory::Normal, 120_Hz},
-                    {30_Hz, FrameRateCategory::NoPreference, 120_Hz},
+                    {24_Hz, FrameRateCategory::High, 120_Hz, kModeId120},
+                    {12_Hz, FrameRateCategory::Normal, 120_Hz, kModeId120},
+                    {30_Hz, FrameRateCategory::NoPreference, 120_Hz, kModeId120},
 
             },
             selector);
 
     testFrameRateCategoryWithMultipleLayers(
             std::initializer_list<Case>{
-                    {24_Hz, FrameRateCategory::Default, 120_Hz},
-                    {30_Hz, FrameRateCategory::Default, 120_Hz},
-                    {120_Hz, FrameRateCategory::Default, 120_Hz},
+                    {24_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+                    {30_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+                    {120_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
             },
             selector);
 }
@@ -1640,6 +1647,7 @@
 
         // Expected result
         Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId120;
     };
 
     testFrameRateCategoryWithMultipleLayers(std::initializer_list<
@@ -1970,6 +1978,122 @@
 }
 
 TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withFrameRateCategory_idleTimer_60_120_nonVrr) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, false);
+    using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
+    struct LayerArg {
+        // Params
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        LayerVoteType voteType = LayerVoteType::ExplicitDefault;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId60;
+    };
+
+    const auto runTest = [&](const TestableRefreshRateSelector& selector,
+                             const std::initializer_list<LayerArg>& layerArgs,
+                             const RefreshRateSelector::GlobalSignals& signals) {
+        std::vector<LayerRequirement> layers;
+        for (auto testCase : layerArgs) {
+            ALOGI("**** %s: Testing frameRateCategory=%s", __func__,
+                  ftl::enum_string(testCase.frameRateCategory).c_str());
+
+            if (testCase.frameRateCategory != FrameRateCategory::Default) {
+                std::stringstream ss;
+                ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = LayerVoteType::ExplicitCategory,
+                                          .frameRateCategory = testCase.frameRateCategory,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            if (testCase.voteType != LayerVoteType::ExplicitDefault) {
+                std::stringstream ss;
+                ss << ftl::enum_string(testCase.voteType);
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = testCase.voteType,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            EXPECT_EQ(testCase.expectedFrameRate,
+                      selector.getBestFrameRateMode(layers, signals).modePtr->getPeakFps())
+                    << "Did not get expected frame rate for"
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+            EXPECT_EQ(testCase.expectedModeId,
+                      selector.getBestFrameRateMode(layers, signals).modePtr->getId())
+                    << "Did not get expected DisplayModeId for modeId="
+                    << ftl::to_underlying(testCase.expectedModeId)
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+        }
+    };
+
+    {
+        // IdleTimer not configured
+        auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120);
+        ASSERT_EQ(0ms, selector.getIdleTimerTimeout());
+
+        runTest(selector,
+                std::initializer_list<LayerArg>{
+                        // Rate does not change due to NoPreference.
+                        {.frameRateCategory = FrameRateCategory::NoPreference,
+                         .expectedFrameRate = 120_Hz,
+                         .expectedModeId = kModeId120},
+                        {.voteType = LayerVoteType::NoVote,
+                         .expectedFrameRate = 120_Hz,
+                         .expectedModeId = kModeId120},
+                        {.frameRateCategory = FrameRateCategory::NoPreference,
+                         .expectedFrameRate = 120_Hz,
+                         .expectedModeId = kModeId120},
+                },
+                {.idle = false});
+    }
+
+    // IdleTimer configured
+    constexpr std::chrono::milliseconds kIdleTimerTimeoutMs = 10ms;
+    auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120,
+                                   Config{
+                                           .idleTimerTimeout = kIdleTimerTimeoutMs,
+                                   });
+    ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+    ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout());
+    runTest(selector,
+            std::initializer_list<LayerArg>{
+                    // Rate won't change immediately and will stay 120 due to NoPreference, as
+                    // idle timer did not timeout yet.
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 120_Hz,
+                     .expectedModeId = kModeId120},
+                    {.voteType = LayerVoteType::NoVote,
+                     .expectedFrameRate = 120_Hz,
+                     .expectedModeId = kModeId120},
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 120_Hz,
+                     .expectedModeId = kModeId120},
+            },
+            {.idle = false});
+
+    // Idle timer is triggered using GlobalSignals.
+    ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+    ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout());
+    runTest(selector,
+            std::initializer_list<LayerArg>{
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 60_Hz,
+                     .expectedModeId = kModeId60},
+                    {.voteType = LayerVoteType::NoVote,
+                     .expectedFrameRate = 60_Hz,
+                     .expectedModeId = kModeId60},
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 60_Hz,
+                     .expectedModeId = kModeId60},
+            },
+            {.idle = true});
+}
+
+TEST_P(RefreshRateSelectorTest,
        getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) {
     if (GetParam() != Config::FrameRateOverride::Enabled) {
         return;
@@ -1992,8 +2116,7 @@
     const std::initializer_list<Case> testCases = {
             // These layers may switch modes because smoothSwitchOnly=false.
             {FrameRateCategory::Default, false, 120_Hz, kModeId120},
-            // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
-            {FrameRateCategory::NoPreference, false, 60_Hz, kModeId60},
+            {FrameRateCategory::NoPreference, false, 120_Hz, kModeId120},
             {FrameRateCategory::Low, false, 30_Hz, kModeId60},
             {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
             {FrameRateCategory::High, false, 120_Hz, kModeId120},
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 1314193..6ba2eb1 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -526,12 +526,15 @@
     *count = num_copied;
 }
 
-PixelFormat GetNativePixelFormat(VkFormat format) {
+PixelFormat GetNativePixelFormat(VkFormat format,
+                                 VkCompositeAlphaFlagBitsKHR alpha) {
     PixelFormat native_format = PixelFormat::RGBA_8888;
     switch (format) {
         case VK_FORMAT_R8G8B8A8_UNORM:
         case VK_FORMAT_R8G8B8A8_SRGB:
-            native_format = PixelFormat::RGBA_8888;
+            native_format = alpha == VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
+                                ? PixelFormat::RGBX_8888
+                                : PixelFormat::RGBA_8888;
             break;
         case VK_FORMAT_R5G6B5_UNORM_PACK16:
             native_format = PixelFormat::RGB_565;
@@ -555,8 +558,7 @@
     return native_format;
 }
 
-DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace,
-                             PixelFormat pixelFormat) {
+DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace, VkFormat format) {
     switch (colorspace) {
         case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
             return DataSpace::SRGB;
@@ -575,7 +577,7 @@
         case VK_COLOR_SPACE_BT709_NONLINEAR_EXT:
             return DataSpace::SRGB;
         case VK_COLOR_SPACE_BT2020_LINEAR_EXT:
-            if (pixelFormat == PixelFormat::RGBA_FP16) {
+            if (format == VK_FORMAT_R16G16B16A16_SFLOAT) {
                 return DataSpace::BT2020_LINEAR_EXTENDED;
             } else {
                 return DataSpace::BT2020_LINEAR;
@@ -764,21 +766,20 @@
         {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
     };
 
+    VkFormat format = VK_FORMAT_UNDEFINED;
     if (colorspace_ext) {
         for (VkColorSpaceKHR colorSpace :
              colorSpaceSupportedByVkEXTSwapchainColorspace) {
-            if (GetNativeDataspace(colorSpace, GetNativePixelFormat(
-                                                   VK_FORMAT_R8G8B8A8_UNORM)) !=
-                DataSpace::UNKNOWN) {
+            format = VK_FORMAT_R8G8B8A8_UNORM;
+            if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) {
                 all_formats.emplace_back(
-                    VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_UNORM, colorSpace});
+                    VkSurfaceFormatKHR{format, colorSpace});
             }
 
-            if (GetNativeDataspace(colorSpace, GetNativePixelFormat(
-                                                   VK_FORMAT_R8G8B8A8_SRGB)) !=
-                DataSpace::UNKNOWN) {
+            format = VK_FORMAT_R8G8B8A8_SRGB;
+            if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) {
                 all_formats.emplace_back(
-                    VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_SRGB, colorSpace});
+                    VkSurfaceFormatKHR{format, colorSpace});
             }
         }
     }
@@ -787,78 +788,73 @@
     // Android users.  This includes the ANGLE team (a layered implementation of
     // OpenGL-ES).
 
+    format = VK_FORMAT_R5G6B5_UNORM_PACK16;
     desc.format = AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+        all_formats.emplace_back(
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
             for (VkColorSpaceKHR colorSpace :
                  colorSpaceSupportedByVkEXTSwapchainColorspace) {
-                if (GetNativeDataspace(
-                        colorSpace,
-                        GetNativePixelFormat(VK_FORMAT_R5G6B5_UNORM_PACK16)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_R5G6B5_UNORM_PACK16, colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
         }
     }
 
+    format = VK_FORMAT_R16G16B16A16_SFLOAT;
     desc.format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT;
     if (AHardwareBuffer_isSupported(&desc)) {
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+        all_formats.emplace_back(
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
             for (VkColorSpaceKHR colorSpace :
                  colorSpaceSupportedByVkEXTSwapchainColorspace) {
-                if (GetNativeDataspace(
-                        colorSpace,
-                        GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
 
             for (
                 VkColorSpaceKHR colorSpace :
                 colorSpaceSupportedByVkEXTSwapchainColorspaceOnFP16SurfaceOnly) {
-                if (GetNativeDataspace(
-                        colorSpace,
-                        GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
         }
     }
 
+    format = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
     desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
         all_formats.emplace_back(
-            VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
-                               VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
             for (VkColorSpaceKHR colorSpace :
                  colorSpaceSupportedByVkEXTSwapchainColorspace) {
-                if (GetNativeDataspace(
-                        colorSpace, GetNativePixelFormat(
-                                        VK_FORMAT_A2B10G10R10_UNORM_PACK32)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_A2B10G10R10_UNORM_PACK32, colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
         }
     }
 
+    format = VK_FORMAT_R8_UNORM;
     desc.format = AHARDWAREBUFFER_FORMAT_R8_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
         if (colorspace_ext) {
-            all_formats.emplace_back(VkSurfaceFormatKHR{
-                VK_FORMAT_R8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+            all_formats.emplace_back(
+                VkSurfaceFormatKHR{format, VK_COLOR_SPACE_PASS_THROUGH_EXT});
         }
     }
 
@@ -877,22 +873,18 @@
             rgba10x6_formats_ext = true;
         }
     }
+    format = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16;
     desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
     if (AHardwareBuffer_isSupported(&desc) && rgba10x6_formats_ext) {
         all_formats.emplace_back(
-            VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
-                               VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
             for (VkColorSpaceKHR colorSpace :
                  colorSpaceSupportedByVkEXTSwapchainColorspace) {
-                if (GetNativeDataspace(
-                        colorSpace,
-                        GetNativePixelFormat(
-                            VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
-                        colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
         }
@@ -912,7 +904,7 @@
     VkSurfaceCapabilities2KHR* pSurfaceCapabilities) {
     ATRACE_CALL();
 
-    auto surface = pSurfaceInfo->surface;
+    auto surface_handle = pSurfaceInfo->surface;
     auto capabilities = &pSurfaceCapabilities->surfaceCapabilities;
 
     VkSurfacePresentModeEXT const *pPresentMode = nullptr;
@@ -933,7 +925,13 @@
     int transform_hint;
     int max_buffer_count;
     int min_undequeued_buffers;
-    if (surface == VK_NULL_HANDLE) {
+    // On Android, window composition is a WindowManager property, not something
+    // associated with the bufferqueue. It can't be changed from here for a
+    // swapchain connected with SurfaceFlinger. For offscreen surfaces, it's
+    // allowed to report opaque being supported for RGBX preference.
+    VkCompositeAlphaFlagsKHR composite_alpha =
+        VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
+    if (surface_handle == VK_NULL_HANDLE) {
         const InstanceData& instance_data = GetData(physicalDevice);
         ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query;
         bool surfaceless_enabled =
@@ -954,7 +952,8 @@
         capabilities->minImageCount = 0xFFFFFFFF;
         capabilities->maxImageCount = 0xFFFFFFFF;
     } else {
-        ANativeWindow* window = SurfaceFromHandle(surface)->window.get();
+        Surface& surface = *SurfaceFromHandle(surface_handle);
+        ANativeWindow* window = surface.window.get();
 
         err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
         if (err != android::OK) {
@@ -1029,6 +1028,11 @@
                     min_undequeued_buffers + default_additional_buffers);
             capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
         }
+
+        if (!(surface.consumer_usage &
+              AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY)) {
+            composite_alpha |= VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+        }
     }
 
     capabilities->currentExtent =
@@ -1055,9 +1059,7 @@
     capabilities->currentTransform =
         TranslateNativeToVulkanTransform(transform_hint);
 
-    // On Android, window composition is a WindowManager property, not something
-    // associated with the bufferqueue. It can't be changed from here.
-    capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
+    capabilities->supportedCompositeAlpha = composite_alpha;
 
     capabilities->supportedUsageFlags =
         VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
@@ -1656,22 +1658,23 @@
 
     ALOGV("vkCreateSwapchainKHR: surface=0x%" PRIx64
           " minImageCount=%u imageFormat=%u imageColorSpace=%u"
-          " imageExtent=%ux%u imageUsage=%#x preTransform=%u presentMode=%u"
-          " oldSwapchain=0x%" PRIx64,
+          " imageExtent=%ux%u imageUsage=%#x preTransform=%u compositeAlpha=%u"
+          " presentMode=%u oldSwapchain=0x%" PRIx64,
           reinterpret_cast<uint64_t>(create_info->surface),
           create_info->minImageCount, create_info->imageFormat,
           create_info->imageColorSpace, create_info->imageExtent.width,
           create_info->imageExtent.height, create_info->imageUsage,
-          create_info->preTransform, create_info->presentMode,
+          create_info->preTransform, create_info->compositeAlpha,
+          create_info->presentMode,
           reinterpret_cast<uint64_t>(create_info->oldSwapchain));
 
     if (!allocator)
         allocator = &GetData(device).allocator;
 
-    PixelFormat native_pixel_format =
-        GetNativePixelFormat(create_info->imageFormat);
-    DataSpace native_dataspace =
-        GetNativeDataspace(create_info->imageColorSpace, native_pixel_format);
+    PixelFormat native_pixel_format = GetNativePixelFormat(
+        create_info->imageFormat, create_info->compositeAlpha);
+    DataSpace native_dataspace = GetNativeDataspace(
+        create_info->imageColorSpace, create_info->imageFormat);
     if (native_dataspace == DataSpace::UNKNOWN) {
         ALOGE(
             "CreateSwapchainKHR(VkSwapchainCreateInfoKHR.imageColorSpace = %d) "