Add batching to IGBP functions

The batching functions has a default implementation that calls
into the implementation of corresponding non-batched version.

Bug: 113788435
Bug: 137244088

Test: atest libgui_test

Change-Id: I068a05b8bfc7c64f893649dec2ef2233f16a22f6
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index af9ef06..c5a1855 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -161,6 +161,7 @@
 filegroup {
     name: "libgui_bufferqueue_sources",
     srcs: [
+        "BatchBufferOps.cpp",
         "BufferItem.cpp",
         "BufferQueue.cpp",
         "BufferQueueConsumer.cpp",
@@ -171,7 +172,7 @@
         "FrameTimestamps.cpp",
         "GLConsumerUtils.cpp",
         "HdrMetadata.cpp",
-        "QueueBufferInputOutput.cpp",
+        "IGraphicBufferProducerFlattenables.cpp",
         "bufferqueue/1.0/Conversion.cpp",
         "bufferqueue/1.0/H2BProducerListener.cpp",
         "bufferqueue/1.0/WProducerListener.cpp",
diff --git a/libs/gui/BatchBufferOps.cpp b/libs/gui/BatchBufferOps.cpp
new file mode 100644
index 0000000..60aceb1
--- /dev/null
+++ b/libs/gui/BatchBufferOps.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2021 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 <inttypes.h>
+
+#define LOG_TAG "IGBPBatchOps"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+
+/**
+ * Default implementation of batched buffer operations. These default
+ * implementations call into the non-batched version of the same operation.
+ */
+
+status_t IGraphicBufferProducer::requestBuffers(
+        const std::vector<int32_t>& slots,
+        std::vector<RequestBufferOutput>* outputs) {
+    outputs->clear();
+    outputs->reserve(slots.size());
+    for (int32_t slot : slots) {
+        RequestBufferOutput& output = outputs->emplace_back();
+        output.result = requestBuffer(static_cast<int>(slot),
+                                      &output.buffer);
+    }
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::dequeueBuffers(
+        const std::vector<DequeueBufferInput>& inputs,
+        std::vector<DequeueBufferOutput>* outputs) {
+    outputs->clear();
+    outputs->reserve(inputs.size());
+    for (const DequeueBufferInput& input : inputs) {
+        DequeueBufferOutput& output = outputs->emplace_back();
+        output.result = dequeueBuffer(
+                &output.slot,
+                &output.fence,
+                input.width,
+                input.height,
+                input.format,
+                input.usage,
+                &output.bufferAge,
+                input.getTimestamps ? &output.timestamps.emplace() : nullptr);
+    }
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::detachBuffers(
+        const std::vector<int32_t>& slots,
+        std::vector<status_t>* results) {
+    results->clear();
+    results->reserve(slots.size());
+    for (int32_t slot : slots) {
+        results->emplace_back(detachBuffer(slot));
+    }
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::attachBuffers(
+        const std::vector<sp<GraphicBuffer>>& buffers,
+        std::vector<AttachBufferOutput>* outputs) {
+    outputs->clear();
+    outputs->reserve(buffers.size());
+    for (const sp<GraphicBuffer>& buffer : buffers) {
+        AttachBufferOutput& output = outputs->emplace_back();
+        output.result = attachBuffer(&output.slot, buffer);
+    }
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::queueBuffers(
+        const std::vector<QueueBufferInput>& inputs,
+        std::vector<QueueBufferOutput>* outputs) {
+    outputs->clear();
+    outputs->reserve(inputs.size());
+    for (const QueueBufferInput& input : inputs) {
+        QueueBufferOutput& output = outputs->emplace_back();
+        output.result = queueBuffer(input.slot, input, &output);
+    }
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::cancelBuffers(
+        const std::vector<CancelBufferInput>& inputs,
+        std::vector<status_t>* results) {
+    results->clear();
+    results->reserve(inputs.size());
+    for (const CancelBufferInput& input : inputs) {
+        results->emplace_back() = cancelBuffer(input.slot, input.fence);
+    }
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::query(const std::vector<int32_t> inputs,
+                                       std::vector<QueryOutput>* outputs) {
+    outputs->clear();
+    outputs->reserve(inputs.size());
+    for (int32_t input : inputs) {
+        QueryOutput& output = outputs->emplace_back();
+        int value{};
+        output.result = static_cast<status_t>(
+                query(static_cast<int>(input), &value));
+        output.value = static_cast<int64_t>(value);
+    }
+    return NO_ERROR;
+}
+
+} // namespace android
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index ad00939..c1f9b85 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -74,6 +74,13 @@
     GET_CONSUMER_USAGE,
     SET_LEGACY_BUFFER_DROP,
     SET_AUTO_PREROTATION,
+    REQUEST_BUFFERS,
+    DEQUEUE_BUFFERS,
+    DETACH_BUFFERS,
+    ATTACH_BUFFERS,
+    QUEUE_BUFFERS,
+    CANCEL_BUFFERS,
+    QUERY_MULTIPLE,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -90,7 +97,7 @@
         Parcel data, reply;
         data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
         data.writeInt32(bufferIdx);
-        status_t result =remote()->transact(REQUEST_BUFFER, data, &reply);
+        status_t result = remote()->transact(REQUEST_BUFFER, data, &reply);
         if (result != NO_ERROR) {
             return result;
         }
@@ -107,6 +114,27 @@
         return result;
     }
 
+    virtual status_t requestBuffers(
+            const std::vector<int32_t>& slots,
+            std::vector<RequestBufferOutput>* outputs) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeInt32Vector(slots);
+        status_t result = remote()->transact(REQUEST_BUFFERS, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.resizeOutVector(outputs);
+        for (RequestBufferOutput& output : *outputs) {
+            if (result != NO_ERROR) {
+                return result;
+            }
+            result = reply.read(output);
+        }
+
+        return result;
+    }
+
     virtual status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers) {
         Parcel data, reply;
         data.writeInterfaceToken(
@@ -183,6 +211,29 @@
         return result;
     }
 
+    virtual status_t dequeueBuffers(
+            const std::vector<DequeueBufferInput>& inputs,
+            std::vector<DequeueBufferOutput>* outputs) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeVectorSize(inputs);
+        for (const auto& input : inputs) {
+            data.write(input);
+        }
+        status_t result = remote()->transact(DEQUEUE_BUFFERS, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.resizeOutVector(outputs);
+        for (auto& output : *outputs) {
+            if (result != NO_ERROR) {
+                return result;
+            }
+            result = reply.read(output);
+        }
+        return result;
+    }
+
     virtual status_t detachBuffer(int slot) {
         Parcel data, reply;
         data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
@@ -195,6 +246,19 @@
         return result;
     }
 
+    virtual status_t detachBuffers(const std::vector<int32_t>& slots,
+                                   std::vector<status_t>* results) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeInt32Vector(slots);
+        status_t result = remote()->transact(DETACH_BUFFERS, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.readInt32Vector(results);
+        return result;
+    }
+
     virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
             sp<Fence>* outFence) {
         if (outBuffer == nullptr) {
@@ -256,6 +320,39 @@
         return result;
     }
 
+    virtual status_t attachBuffers(
+            const std::vector<sp<GraphicBuffer>>& buffers,
+            std::vector<AttachBufferOutput>* outputs) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeVectorSize(buffers);
+        for (const sp<GraphicBuffer>& buffer : buffers) {
+            data.write(*buffer.get());
+        }
+        status_t result = remote()->transact(ATTACH_BUFFERS, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.resizeOutVector(outputs);
+        for (AttachBufferOutput& output : *outputs) {
+            if (result != NO_ERROR) {
+                return result;
+            }
+            result = reply.read(output);
+        }
+        if (result == NO_ERROR) {
+            for (AttachBufferOutput& output : *outputs) {
+                if (output.result == NO_ERROR && output.slot < 0) {
+                    ALOGE("attachBuffers returned invalid slot %d",
+                          output.slot);
+                    android_errorWriteLog(0x534e4554, "37478824");
+                    output.result = UNKNOWN_ERROR;
+                }
+            }
+        }
+        return result;
+    }
+
     virtual status_t queueBuffer(int buf,
             const QueueBufferInput& input, QueueBufferOutput* output) {
         Parcel data, reply;
@@ -278,6 +375,28 @@
         return result;
     }
 
+    virtual status_t queueBuffers(const std::vector<QueueBufferInput>& inputs,
+                                  std::vector<QueueBufferOutput>* outputs) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeVectorSize(inputs);
+        for (const QueueBufferInput& input : inputs) {
+            data.write(input);
+        }
+        status_t result = remote()->transact(QUEUE_BUFFERS, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.resizeOutVector(outputs);
+        for (QueueBufferOutput& output : *outputs) {
+            if (result != NO_ERROR) {
+                return result;
+            }
+            result = reply.read(output);
+        }
+        return result;
+    }
+
     virtual status_t cancelBuffer(int buf, const sp<Fence>& fence) {
         Parcel data, reply;
         data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
@@ -291,6 +410,23 @@
         return result;
     }
 
+    virtual status_t cancelBuffers(
+            const std::vector<CancelBufferInput>& inputs,
+            std::vector<status_t>* results) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeVectorSize(inputs);
+        for (const CancelBufferInput& input : inputs) {
+            data.write(input);
+        }
+        status_t result = remote()->transact(CANCEL_BUFFERS, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.readInt32Vector(results);
+        return result;
+    }
+
     virtual int query(int what, int* value) {
         Parcel data, reply;
         data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
@@ -304,6 +440,25 @@
         return result;
     }
 
+    virtual status_t query(const std::vector<int32_t> inputs,
+                           std::vector<QueryOutput>* outputs) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeInt32Vector(inputs);
+        status_t result = remote()->transact(QUERY_MULTIPLE, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.resizeOutVector(outputs);
+        for (QueryOutput& output : *outputs) {
+            if (result != NO_ERROR) {
+                return result;
+            }
+            result = reply.read(output);
+        }
+        return result;
+    }
+
     virtual status_t connect(const sp<IProducerListener>& listener,
             int api, bool producerControlledByApp, QueueBufferOutput* output) {
         Parcel data, reply;
@@ -576,6 +731,12 @@
         return mBase->requestBuffer(slot, buf);
     }
 
+    status_t requestBuffers(
+            const std::vector<int32_t>& slots,
+            std::vector<RequestBufferOutput>* outputs) override {
+        return mBase->requestBuffers(slots, outputs);
+    }
+
     status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers) override {
         return mBase->setMaxDequeuedBufferCount(maxDequeuedBuffers);
     }
@@ -590,10 +751,21 @@
         return mBase->dequeueBuffer(slot, fence, w, h, format, usage, outBufferAge, outTimestamps);
     }
 
+    status_t dequeueBuffers(
+            const std::vector<DequeueBufferInput>& inputs,
+            std::vector<DequeueBufferOutput>* outputs) override {
+        return mBase->dequeueBuffers(inputs, outputs);
+    }
+
     status_t detachBuffer(int slot) override {
         return mBase->detachBuffer(slot);
     }
 
+    status_t detachBuffers(const std::vector<int32_t>& slots,
+                           std::vector<status_t>* results) override {
+        return mBase->detachBuffers(slots, results);
+    }
+
     status_t detachNextBuffer(
             sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence) override {
         return mBase->detachNextBuffer(outBuffer, outFence);
@@ -604,6 +776,12 @@
         return mBase->attachBuffer(outSlot, buffer);
     }
 
+    status_t attachBuffers(
+            const std::vector<sp<GraphicBuffer>>& buffers,
+            std::vector<AttachBufferOutput>* outputs) override {
+        return mBase->attachBuffers(buffers, outputs);
+    }
+
     status_t queueBuffer(
             int slot,
             const QueueBufferInput& input,
@@ -611,14 +789,30 @@
         return mBase->queueBuffer(slot, input, output);
     }
 
+    status_t queueBuffers(const std::vector<QueueBufferInput>& inputs,
+                          std::vector<QueueBufferOutput>* outputs) override {
+        return mBase->queueBuffers(inputs, outputs);
+    }
+
     status_t cancelBuffer(int slot, const sp<Fence>& fence) override {
         return mBase->cancelBuffer(slot, fence);
     }
 
+    status_t cancelBuffers(
+            const std::vector<CancelBufferInput>& inputs,
+            std::vector<status_t>* results) override {
+        return mBase->cancelBuffers(inputs, results);
+    }
+
     int query(int what, int* value) override {
         return mBase->query(what, value);
     }
 
+    status_t query(const std::vector<int32_t> inputs,
+                   std::vector<QueryOutput>* outputs) override {
+        return mBase->query(inputs, outputs);
+    }
+
     status_t connect(
             const sp<IProducerListener>& listener,
             int api, bool producerControlledByApp,
@@ -789,7 +983,7 @@
     switch(code) {
         case REQUEST_BUFFER: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
-            int bufferIdx   = data.readInt32();
+            int bufferIdx = data.readInt32();
             sp<GraphicBuffer> buffer;
             int result = requestBuffer(bufferIdx, &buffer);
             reply->writeInt32(buffer != nullptr);
@@ -799,6 +993,24 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
+        case REQUEST_BUFFERS: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            std::vector<int32_t> slots;
+            std::vector<RequestBufferOutput> outputs;
+            status_t result = data.readInt32Vector(&slots);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            (void)requestBuffers(slots, &outputs);
+            result = reply->writeVectorSize(outputs);
+            for (const RequestBufferOutput& output : outputs) {
+                if (result != NO_ERROR) {
+                    return result;
+                }
+                result = reply->write(output);
+            }
+            return result;
+        }
         case SET_MAX_DEQUEUED_BUFFER_COUNT: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
             int maxDequeuedBuffers = data.readInt32();
@@ -841,6 +1053,30 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
+        case DEQUEUE_BUFFERS: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            std::vector<DequeueBufferInput> inputs;
+            std::vector<DequeueBufferOutput> outputs;
+            status_t result = data.resizeOutVector(&inputs);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            for (DequeueBufferInput& input : inputs) {
+                result = data.read(input);
+                if (result != NO_ERROR) {
+                    return result;
+                }
+            }
+            (void)dequeueBuffers(inputs, &outputs);
+            result = reply->writeVectorSize(outputs);
+            for (const DequeueBufferOutput& output : outputs) {
+                if (result != NO_ERROR) {
+                    return result;
+                }
+                result = reply->write(output);
+            }
+            return result;
+        }
         case DETACH_BUFFER: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
             int slot = data.readInt32();
@@ -848,6 +1084,17 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
+        case DETACH_BUFFERS: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            std::vector<int32_t> slots;
+            std::vector<status_t> results;
+            status_t result = data.readInt32Vector(&slots);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            (void)detachBuffers(slots, &results);
+            return reply->writeInt32Vector(results);
+        }
         case DETACH_NEXT_BUFFER: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
             sp<GraphicBuffer> buffer;
@@ -878,6 +1125,31 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
+        case ATTACH_BUFFERS: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            std::vector<sp<GraphicBuffer>> buffers;
+            status_t result = data.resizeOutVector(&buffers);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            for (sp<GraphicBuffer>& buffer : buffers) {
+                buffer = new GraphicBuffer();
+                result = data.read(*buffer.get());
+                if (result != NO_ERROR) {
+                    return result;
+                }
+            }
+            std::vector<AttachBufferOutput> outputs;
+            (void)attachBuffers(buffers, &outputs);
+            result = reply->writeVectorSize(outputs);
+            for (const AttachBufferOutput& output : outputs) {
+                if (result != NO_ERROR) {
+                    return result;
+                }
+                result = reply->write(output);
+            }
+            return result;
+        }
         case QUEUE_BUFFER: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
 
@@ -890,6 +1162,30 @@
 
             return NO_ERROR;
         }
+        case QUEUE_BUFFERS: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            std::vector<QueueBufferInput> inputs;
+            status_t result = data.resizeOutVector(&inputs);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            for (QueueBufferInput& input : inputs) {
+                result = data.read(input);
+                if (result != NO_ERROR) {
+                    return result;
+                }
+            }
+            std::vector<QueueBufferOutput> outputs;
+            (void)queueBuffers(inputs, &outputs);
+            result = reply->writeVectorSize(outputs);
+            for (const QueueBufferOutput& output : outputs) {
+                if (result != NO_ERROR) {
+                    return result;
+                }
+                result = reply->write(output);
+            }
+            return result;
+        }
         case CANCEL_BUFFER: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
             int buf = data.readInt32();
@@ -901,6 +1197,26 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
+        case CANCEL_BUFFERS: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            std::vector<CancelBufferInput> inputs;
+            status_t result = data.resizeOutVector(&inputs);
+            for (CancelBufferInput& input : inputs) {
+                if (result != NO_ERROR) {
+                    return result;
+                }
+                result = data.read(input);
+            }
+            if (result != NO_ERROR) {
+                return result;
+            }
+            std::vector<status_t> results;
+            result = cancelBuffers(inputs, &results);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            return reply->writeInt32Vector(results);
+        }
         case QUERY: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
             int value = 0;
@@ -910,6 +1226,27 @@
             reply->writeInt32(res);
             return NO_ERROR;
         }
+        case QUERY_MULTIPLE: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            std::vector<int32_t> inputs;
+            status_t result = data.readInt32Vector(&inputs);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            std::vector<QueryOutput> outputs;
+            result = query(inputs, &outputs);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            result = reply->writeVectorSize(outputs);
+            for (const QueryOutput& output : outputs) {
+                if (result != NO_ERROR) {
+                    return result;
+                }
+                result = reply->write(output);
+            }
+            return result;
+        }
         case CONNECT: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
             sp<IProducerListener> listener;
@@ -1083,11 +1420,4 @@
     return BBinder::onTransact(code, data, reply, flags);
 }
 
-// ----------------------------------------------------------------------------
-
-IGraphicBufferProducer::QueueBufferInput::QueueBufferInput(const Parcel& parcel) {
-    parcel.read(*this);
-}
-
-
 }; // namespace android
diff --git a/libs/gui/IGraphicBufferProducerFlattenables.cpp b/libs/gui/IGraphicBufferProducerFlattenables.cpp
new file mode 100644
index 0000000..c8b9b67
--- /dev/null
+++ b/libs/gui/IGraphicBufferProducerFlattenables.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2021 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 <inttypes.h>
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+
+constexpr size_t IGraphicBufferProducer::QueueBufferInput::minFlattenedSize() {
+    return sizeof(timestamp) +
+            sizeof(isAutoTimestamp) +
+            sizeof(dataSpace) +
+            sizeof(crop) +
+            sizeof(scalingMode) +
+            sizeof(transform) +
+            sizeof(stickyTransform) +
+            sizeof(getFrameTimestamps) +
+            sizeof(slot);
+}
+
+size_t IGraphicBufferProducer::QueueBufferInput::getFlattenedSize() const {
+    return minFlattenedSize() +
+            fence->getFlattenedSize() +
+            surfaceDamage.getFlattenedSize() +
+            hdrMetadata.getFlattenedSize();
+}
+
+size_t IGraphicBufferProducer::QueueBufferInput::getFdCount() const {
+    return fence->getFdCount();
+}
+
+status_t IGraphicBufferProducer::QueueBufferInput::flatten(
+        void*& buffer, size_t& size, int*& fds, size_t& count) const
+{
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::write(buffer, size, timestamp);
+    FlattenableUtils::write(buffer, size, isAutoTimestamp);
+    FlattenableUtils::write(buffer, size, dataSpace);
+    FlattenableUtils::write(buffer, size, crop);
+    FlattenableUtils::write(buffer, size, scalingMode);
+    FlattenableUtils::write(buffer, size, transform);
+    FlattenableUtils::write(buffer, size, stickyTransform);
+    FlattenableUtils::write(buffer, size, getFrameTimestamps);
+
+    status_t result = fence->flatten(buffer, size, fds, count);
+    if (result != NO_ERROR) {
+        return result;
+    }
+    result = surfaceDamage.flatten(buffer, size);
+    if (result != NO_ERROR) {
+        return result;
+    }
+    FlattenableUtils::advance(buffer, size, surfaceDamage.getFlattenedSize());
+    result = hdrMetadata.flatten(buffer, size);
+    if (result != NO_ERROR) {
+        return result;
+    }
+    FlattenableUtils::advance(buffer, size, hdrMetadata.getFlattenedSize());
+    FlattenableUtils::write(buffer, size, slot);
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::QueueBufferInput::unflatten(
+        void const*& buffer, size_t& size, int const*& fds, size_t& count)
+{
+    if (size < minFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::read(buffer, size, timestamp);
+    FlattenableUtils::read(buffer, size, isAutoTimestamp);
+    FlattenableUtils::read(buffer, size, dataSpace);
+    FlattenableUtils::read(buffer, size, crop);
+    FlattenableUtils::read(buffer, size, scalingMode);
+    FlattenableUtils::read(buffer, size, transform);
+    FlattenableUtils::read(buffer, size, stickyTransform);
+    FlattenableUtils::read(buffer, size, getFrameTimestamps);
+
+    fence = new Fence();
+    status_t result = fence->unflatten(buffer, size, fds, count);
+    if (result != NO_ERROR) {
+        return result;
+    }
+    result = surfaceDamage.unflatten(buffer, size);
+    if (result != NO_ERROR) {
+        return result;
+    }
+    FlattenableUtils::advance(buffer, size, surfaceDamage.getFlattenedSize());
+    result =  hdrMetadata.unflatten(buffer, size);
+    if (result != NO_ERROR) {
+        return result;
+    }
+    FlattenableUtils::advance(buffer, size, hdrMetadata.getFlattenedSize());
+    FlattenableUtils::read(buffer, size, slot);
+    return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+constexpr size_t IGraphicBufferProducer::QueueBufferOutput::minFlattenedSize() {
+    return sizeof(width) + sizeof(height) + sizeof(transformHint) + sizeof(numPendingBuffers) +
+            sizeof(nextFrameNumber) + sizeof(bufferReplaced) + sizeof(maxBufferCount) +
+            sizeof(result);
+}
+size_t IGraphicBufferProducer::QueueBufferOutput::getFlattenedSize() const {
+    return minFlattenedSize() + frameTimestamps.getFlattenedSize();
+}
+
+size_t IGraphicBufferProducer::QueueBufferOutput::getFdCount() const {
+    return frameTimestamps.getFdCount();
+}
+
+status_t IGraphicBufferProducer::QueueBufferOutput::flatten(
+        void*& buffer, size_t& size, int*& fds, size_t& count) const
+{
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::write(buffer, size, width);
+    FlattenableUtils::write(buffer, size, height);
+    FlattenableUtils::write(buffer, size, transformHint);
+    FlattenableUtils::write(buffer, size, numPendingBuffers);
+    FlattenableUtils::write(buffer, size, nextFrameNumber);
+    FlattenableUtils::write(buffer, size, bufferReplaced);
+    FlattenableUtils::write(buffer, size, maxBufferCount);
+
+    status_t result = frameTimestamps.flatten(buffer, size, fds, count);
+    if (result != NO_ERROR) {
+        return result;
+    }
+    FlattenableUtils::write(buffer, size, result);
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::QueueBufferOutput::unflatten(
+        void const*& buffer, size_t& size, int const*& fds, size_t& count)
+{
+    if (size < minFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::read(buffer, size, width);
+    FlattenableUtils::read(buffer, size, height);
+    FlattenableUtils::read(buffer, size, transformHint);
+    FlattenableUtils::read(buffer, size, numPendingBuffers);
+    FlattenableUtils::read(buffer, size, nextFrameNumber);
+    FlattenableUtils::read(buffer, size, bufferReplaced);
+    FlattenableUtils::read(buffer, size, maxBufferCount);
+
+    status_t result = frameTimestamps.unflatten(buffer, size, fds, count);
+    if (result != NO_ERROR) {
+        return result;
+    }
+    FlattenableUtils::read(buffer, size, result);
+    return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+constexpr size_t IGraphicBufferProducer::RequestBufferOutput::minFlattenedSize() {
+    return sizeof(result) +
+            sizeof(int32_t); // IsBufferNull
+}
+
+size_t IGraphicBufferProducer::RequestBufferOutput::getFlattenedSize() const {
+    return minFlattenedSize() + (buffer == nullptr ? 0 : buffer->getFlattenedSize());
+}
+
+size_t IGraphicBufferProducer::RequestBufferOutput::getFdCount() const {
+    return (buffer == nullptr ? 0 : buffer->getFdCount());
+}
+
+status_t IGraphicBufferProducer::RequestBufferOutput::flatten(
+        void*& fBuffer, size_t& size, int*& fds, size_t& count) const {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::write(fBuffer, size, result);
+    const int32_t isBufferNull = (buffer == nullptr ? 1 : 0);
+    FlattenableUtils::write(fBuffer, size, isBufferNull);
+
+    if (!isBufferNull) {
+        status_t status = buffer->flatten(fBuffer, size, fds, count);
+        if (status != NO_ERROR) {
+            return status;
+        }
+    }
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::RequestBufferOutput::unflatten(
+        void const*& fBuffer, size_t& size, int const*& fds, size_t& count) {
+    if (size < minFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::read(fBuffer, size, result);
+    int32_t isBufferNull = 0;
+    FlattenableUtils::read(fBuffer, size, isBufferNull);
+    buffer = new GraphicBuffer();
+    if (!isBufferNull) {
+        status_t status = buffer->unflatten(fBuffer, size, fds, count);
+        if (status != NO_ERROR) {
+            return status;
+        }
+    }
+    return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+size_t IGraphicBufferProducer::DequeueBufferInput::getFlattenedSize() const {
+    return sizeof(width) + sizeof(height) + sizeof(format) + sizeof(usage) +
+            sizeof(int32_t/*getTimestamps*/);
+}
+
+status_t IGraphicBufferProducer::DequeueBufferInput::flatten(void* buffer, size_t size) const {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+    FlattenableUtils::write(buffer, size, width);
+    FlattenableUtils::write(buffer, size, height);
+    FlattenableUtils::write(buffer, size, format);
+    FlattenableUtils::write(buffer, size, usage);
+    const int32_t getTimestampsInt = (getTimestamps ? 1 : 0);
+    FlattenableUtils::write(buffer, size, getTimestampsInt);
+
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::DequeueBufferInput::unflatten(void const* buffer, size_t size) {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::read(buffer, size, width);
+    FlattenableUtils::read(buffer, size, height);
+    FlattenableUtils::read(buffer, size, format);
+    FlattenableUtils::read(buffer, size, usage);
+    int32_t getTimestampsInt = 0;
+    FlattenableUtils::read(buffer, size, getTimestampsInt);
+    getTimestamps = (getTimestampsInt == 1);
+
+    return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+constexpr size_t IGraphicBufferProducer::DequeueBufferOutput::minFlattenedSize() {
+    return sizeof(result) + sizeof(slot) + sizeof(bufferAge) + sizeof(int32_t/*hasTimestamps*/);
+}
+
+size_t IGraphicBufferProducer::DequeueBufferOutput::getFlattenedSize() const {
+    return minFlattenedSize() +
+            fence->getFlattenedSize() +
+            (timestamps.has_value() ? timestamps->getFlattenedSize() : 0);
+}
+
+size_t IGraphicBufferProducer::DequeueBufferOutput::getFdCount() const {
+    return fence->getFdCount() +
+            (timestamps.has_value() ? timestamps->getFdCount() : 0);
+}
+
+status_t IGraphicBufferProducer::DequeueBufferOutput::flatten(
+        void*& buffer, size_t& size, int*& fds, size_t& count) const {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::write(buffer, size, result);
+    FlattenableUtils::write(buffer, size, slot);
+    FlattenableUtils::write(buffer, size, bufferAge);
+    status_t status = fence->flatten(buffer, size, fds, count);
+    if (status != NO_ERROR) {
+        return result;
+    }
+    const int32_t hasTimestamps = timestamps.has_value() ? 1 : 0;
+    FlattenableUtils::write(buffer, size, hasTimestamps);
+    if (timestamps.has_value()) {
+        status = timestamps->flatten(buffer, size, fds, count);
+    }
+    return status;
+}
+
+status_t IGraphicBufferProducer::DequeueBufferOutput::unflatten(
+        void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+    if (size < minFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::read(buffer, size, result);
+    FlattenableUtils::read(buffer, size, slot);
+    FlattenableUtils::read(buffer, size, bufferAge);
+
+    fence = new Fence();
+    status_t status = fence->unflatten(buffer, size, fds, count);
+    if (status != NO_ERROR) {
+        return status;
+    }
+    int32_t hasTimestamps = 0;
+    FlattenableUtils::read(buffer, size, hasTimestamps);
+    if (hasTimestamps) {
+        timestamps.emplace();
+        status = timestamps->unflatten(buffer, size, fds, count);
+    }
+    return status;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+size_t IGraphicBufferProducer::AttachBufferOutput::getFlattenedSize() const {
+    return sizeof(result) + sizeof(slot);
+}
+
+status_t IGraphicBufferProducer::AttachBufferOutput::flatten(void* buffer, size_t size) const {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+    FlattenableUtils::write(buffer, size, result);
+    FlattenableUtils::write(buffer, size, slot);
+
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::AttachBufferOutput::unflatten(void const* buffer, size_t size) {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+    FlattenableUtils::read(buffer, size, result);
+    FlattenableUtils::read(buffer, size, slot);
+
+    return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+constexpr size_t IGraphicBufferProducer::CancelBufferInput::minFlattenedSize() {
+    return sizeof(slot);
+}
+
+size_t IGraphicBufferProducer::CancelBufferInput::getFlattenedSize() const {
+    return minFlattenedSize() + fence->getFlattenedSize();
+}
+
+size_t IGraphicBufferProducer::CancelBufferInput::getFdCount() const {
+    return fence->getFdCount();
+}
+
+status_t IGraphicBufferProducer::CancelBufferInput::flatten(
+        void*& buffer, size_t& size, int*& fds, size_t& count) const {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::write(buffer, size, slot);
+    return fence->flatten(buffer, size, fds, count);
+}
+
+status_t IGraphicBufferProducer::CancelBufferInput::unflatten(
+        void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+    if (size < minFlattenedSize()) {
+        return NO_MEMORY;
+    }
+
+    FlattenableUtils::read(buffer, size, slot);
+
+    fence = new Fence();
+    return fence->unflatten(buffer, size, fds, count);
+}
+
+////////////////////////////////////////////////////////////////////////
+
+size_t IGraphicBufferProducer::QueryOutput::getFlattenedSize() const {
+    return sizeof(result) + sizeof(value);
+}
+
+status_t IGraphicBufferProducer::QueryOutput::flatten(void* buffer, size_t size) const {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+    FlattenableUtils::write(buffer, size, result);
+    FlattenableUtils::write(buffer, size, value);
+
+    return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::QueryOutput::unflatten(void const* buffer, size_t size) {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+    FlattenableUtils::read(buffer, size, result);
+    FlattenableUtils::read(buffer, size, value);
+
+    return NO_ERROR;
+}
+
+} // namespace android
diff --git a/libs/gui/QueueBufferInputOutput.cpp b/libs/gui/QueueBufferInputOutput.cpp
deleted file mode 100644
index 30f0ef6..0000000
--- a/libs/gui/QueueBufferInputOutput.cpp
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2010 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 <inttypes.h>
-
-#define LOG_TAG "QueueBufferInputOutput"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-//#define LOG_NDEBUG 0
-
-#include <gui/IGraphicBufferProducer.h>
-
-namespace android {
-
-constexpr size_t IGraphicBufferProducer::QueueBufferInput::minFlattenedSize() {
-    return sizeof(timestamp) +
-            sizeof(isAutoTimestamp) +
-            sizeof(dataSpace) +
-            sizeof(crop) +
-            sizeof(scalingMode) +
-            sizeof(transform) +
-            sizeof(stickyTransform) +
-            sizeof(getFrameTimestamps);
-}
-
-IGraphicBufferProducer::QueueBufferInput::QueueBufferInput(const Parcel& parcel) {
-    parcel.read(*this);
-}
-
-size_t IGraphicBufferProducer::QueueBufferInput::getFlattenedSize() const {
-    return minFlattenedSize() +
-            fence->getFlattenedSize() +
-            surfaceDamage.getFlattenedSize() +
-            hdrMetadata.getFlattenedSize();
-}
-
-size_t IGraphicBufferProducer::QueueBufferInput::getFdCount() const {
-    return fence->getFdCount();
-}
-
-status_t IGraphicBufferProducer::QueueBufferInput::flatten(
-        void*& buffer, size_t& size, int*& fds, size_t& count) const
-{
-    if (size < getFlattenedSize()) {
-        return NO_MEMORY;
-    }
-
-    FlattenableUtils::write(buffer, size, timestamp);
-    FlattenableUtils::write(buffer, size, isAutoTimestamp);
-    FlattenableUtils::write(buffer, size, dataSpace);
-    FlattenableUtils::write(buffer, size, crop);
-    FlattenableUtils::write(buffer, size, scalingMode);
-    FlattenableUtils::write(buffer, size, transform);
-    FlattenableUtils::write(buffer, size, stickyTransform);
-    FlattenableUtils::write(buffer, size, getFrameTimestamps);
-
-    status_t result = fence->flatten(buffer, size, fds, count);
-    if (result != NO_ERROR) {
-        return result;
-    }
-    result = surfaceDamage.flatten(buffer, size);
-    if (result != NO_ERROR) {
-        return result;
-    }
-    FlattenableUtils::advance(buffer, size, surfaceDamage.getFlattenedSize());
-    return hdrMetadata.flatten(buffer, size);
-}
-
-status_t IGraphicBufferProducer::QueueBufferInput::unflatten(
-        void const*& buffer, size_t& size, int const*& fds, size_t& count)
-{
-    if (size < minFlattenedSize()) {
-        return NO_MEMORY;
-    }
-
-    FlattenableUtils::read(buffer, size, timestamp);
-    FlattenableUtils::read(buffer, size, isAutoTimestamp);
-    FlattenableUtils::read(buffer, size, dataSpace);
-    FlattenableUtils::read(buffer, size, crop);
-    FlattenableUtils::read(buffer, size, scalingMode);
-    FlattenableUtils::read(buffer, size, transform);
-    FlattenableUtils::read(buffer, size, stickyTransform);
-    FlattenableUtils::read(buffer, size, getFrameTimestamps);
-
-    fence = new Fence();
-    status_t result = fence->unflatten(buffer, size, fds, count);
-    if (result != NO_ERROR) {
-        return result;
-    }
-    result = surfaceDamage.unflatten(buffer, size);
-    if (result != NO_ERROR) {
-        return result;
-    }
-    FlattenableUtils::advance(buffer, size, surfaceDamage.getFlattenedSize());
-    return hdrMetadata.unflatten(buffer, size);
-}
-
-////////////////////////////////////////////////////////////////////////
-constexpr size_t IGraphicBufferProducer::QueueBufferOutput::minFlattenedSize() {
-    return sizeof(width) + sizeof(height) + sizeof(transformHint) + sizeof(numPendingBuffers) +
-            sizeof(nextFrameNumber) + sizeof(bufferReplaced) + sizeof(maxBufferCount);
-}
-size_t IGraphicBufferProducer::QueueBufferOutput::getFlattenedSize() const {
-    return minFlattenedSize() + frameTimestamps.getFlattenedSize();
-}
-
-size_t IGraphicBufferProducer::QueueBufferOutput::getFdCount() const {
-    return frameTimestamps.getFdCount();
-}
-
-status_t IGraphicBufferProducer::QueueBufferOutput::flatten(
-        void*& buffer, size_t& size, int*& fds, size_t& count) const
-{
-    if (size < getFlattenedSize()) {
-        return NO_MEMORY;
-    }
-
-    FlattenableUtils::write(buffer, size, width);
-    FlattenableUtils::write(buffer, size, height);
-    FlattenableUtils::write(buffer, size, transformHint);
-    FlattenableUtils::write(buffer, size, numPendingBuffers);
-    FlattenableUtils::write(buffer, size, nextFrameNumber);
-    FlattenableUtils::write(buffer, size, bufferReplaced);
-    FlattenableUtils::write(buffer, size, maxBufferCount);
-
-    return frameTimestamps.flatten(buffer, size, fds, count);
-}
-
-status_t IGraphicBufferProducer::QueueBufferOutput::unflatten(
-        void const*& buffer, size_t& size, int const*& fds, size_t& count)
-{
-    if (size < minFlattenedSize()) {
-        return NO_MEMORY;
-    }
-
-    FlattenableUtils::read(buffer, size, width);
-    FlattenableUtils::read(buffer, size, height);
-    FlattenableUtils::read(buffer, size, transformHint);
-    FlattenableUtils::read(buffer, size, numPendingBuffers);
-    FlattenableUtils::read(buffer, size, nextFrameNumber);
-    FlattenableUtils::read(buffer, size, bufferReplaced);
-    FlattenableUtils::read(buffer, size, maxBufferCount);
-
-    return frameTimestamps.unflatten(buffer, size, fds, count);
-}
-
-} // namespace android
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 45e0a13..c3b9262 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -38,6 +38,9 @@
 #include <android/hardware/graphics/bufferqueue/1.0/IGraphicBufferProducer.h>
 #include <android/hardware/graphics/bufferqueue/2.0/IGraphicBufferProducer.h>
 
+#include <optional>
+#include <vector>
+
 namespace android {
 // ----------------------------------------------------------------------------
 
@@ -289,8 +292,9 @@
             const sp<GraphicBuffer>& buffer) = 0;
 
     struct QueueBufferInput : public Flattenable<QueueBufferInput> {
-        friend class Flattenable<QueueBufferInput>;
-        explicit inline QueueBufferInput(const Parcel& parcel);
+        explicit inline QueueBufferInput(const Parcel& parcel) {
+            parcel.read(*this);
+        }
 
         // timestamp - a monotonically increasing value in nanoseconds
         // isAutoTimestamp - if the timestamp was synthesized at queue time
@@ -304,21 +308,29 @@
         //          camera mode).
         // getFrameTimestamps - whether or not the latest frame timestamps
         //                      should be retrieved from the consumer.
+        // slot - the slot index to queue. This is used only by queueBuffers().
+        //        queueBuffer() ignores this value and uses the argument `slot`
+        //        instead.
         inline QueueBufferInput(int64_t _timestamp, bool _isAutoTimestamp,
                 android_dataspace _dataSpace, const Rect& _crop,
                 int _scalingMode, uint32_t _transform, const sp<Fence>& _fence,
-                uint32_t _sticky = 0, bool _getFrameTimestamps = false)
+                uint32_t _sticky = 0, bool _getFrameTimestamps = false,
+                int _slot = -1)
                 : timestamp(_timestamp), isAutoTimestamp(_isAutoTimestamp),
                   dataSpace(_dataSpace), crop(_crop), scalingMode(_scalingMode),
-                  transform(_transform), stickyTransform(_sticky), fence(_fence),
-                  surfaceDamage(), getFrameTimestamps(_getFrameTimestamps) { }
+                  transform(_transform), stickyTransform(_sticky),
+                  fence(_fence), surfaceDamage(),
+                  getFrameTimestamps(_getFrameTimestamps), slot(_slot) { }
+
+        QueueBufferInput() = default;
 
         inline void deflate(int64_t* outTimestamp, bool* outIsAutoTimestamp,
                 android_dataspace* outDataSpace,
                 Rect* outCrop, int* outScalingMode,
                 uint32_t* outTransform, sp<Fence>* outFence,
                 uint32_t* outStickyTransform = nullptr,
-                bool* outGetFrameTimestamps = nullptr) const {
+                bool* outGetFrameTimestamps = nullptr,
+                int* outSlot = nullptr) const {
             *outTimestamp = timestamp;
             *outIsAutoTimestamp = bool(isAutoTimestamp);
             *outDataSpace = dataSpace;
@@ -332,6 +344,9 @@
             if (outGetFrameTimestamps) {
                 *outGetFrameTimestamps = getFrameTimestamps;
             }
+            if (outSlot) {
+                *outSlot = slot;
+            }
         }
 
         // Flattenable protocol
@@ -357,6 +372,7 @@
         sp<Fence> fence;
         Region surfaceDamage;
         bool getFrameTimestamps{false};
+        int slot{-1};
         HdrMetadata hdrMetadata;
     };
 
@@ -385,6 +401,7 @@
         FrameEventHistoryDelta frameTimestamps;
         bool bufferReplaced{false};
         int maxBufferCount{0};
+        status_t result{NO_ERROR};
     };
 
     // queueBuffer indicates that the client has finished filling in the
@@ -404,6 +421,10 @@
     // Upon success, the output will be filled with meaningful values
     // (refer to the documentation below).
     //
+    // Note: QueueBufferInput::slot was added to QueueBufferInput to be used by
+    // queueBuffers(), the batched version of queueBuffer(). The non-batched
+    // method (queueBuffer()) uses `slot` and ignores `input.slot`.
+    //
     // Return of a value other than NO_ERROR means an error has occurred:
     // * NO_INIT - the buffer queue has been abandoned or the producer is not
     //             connected.
@@ -639,6 +660,147 @@
     // the width and height used for dequeueBuffer will be additionally swapped.
     virtual status_t setAutoPrerotation(bool autoPrerotation);
 
+    struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
+        RequestBufferOutput() = default;
+
+        // Flattenable protocol
+        static constexpr size_t minFlattenedSize();
+        size_t getFlattenedSize() const;
+        size_t getFdCount() const;
+        status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+        status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+        status_t result;
+        sp<GraphicBuffer> buffer;
+    };
+
+    // Batched version of requestBuffer().
+    // This method behaves like a sequence of requestBuffer() calls.
+    // The return value of the batched method will only be about the
+    // transaction. For a local call, the return value will always be NO_ERROR.
+    virtual status_t requestBuffers(
+            const std::vector<int32_t>& slots,
+            std::vector<RequestBufferOutput>* outputs);
+
+    struct DequeueBufferInput : public LightFlattenable<DequeueBufferInput> {
+        DequeueBufferInput() = default;
+
+        // LightFlattenable protocol
+        inline bool isFixedSize() const { return true; }
+        size_t getFlattenedSize() const;
+        status_t flatten(void* buffer, size_t size) const;
+        status_t unflatten(void const* buffer, size_t size);
+
+        uint32_t width;
+        uint32_t height;
+        PixelFormat format;
+        uint64_t usage;
+        bool getTimestamps;
+    };
+
+    struct DequeueBufferOutput : public Flattenable<DequeueBufferOutput> {
+        DequeueBufferOutput() = default;
+
+        // Flattenable protocol
+        static constexpr size_t minFlattenedSize();
+        size_t getFlattenedSize() const;
+        size_t getFdCount() const;
+        status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+        status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+        status_t result;
+        int slot = -1;
+        sp<Fence> fence = Fence::NO_FENCE;
+        uint64_t bufferAge;
+        std::optional<FrameEventHistoryDelta> timestamps;
+    };
+
+    // Batched version of dequeueBuffer().
+    // This method behaves like a sequence of dequeueBuffer() calls.
+    // The return value of the batched method will only be about the
+    // transaction. For a local call, the return value will always be NO_ERROR.
+    virtual status_t dequeueBuffers(
+            const std::vector<DequeueBufferInput>& inputs,
+            std::vector<DequeueBufferOutput>* outputs);
+
+    // Batched version of detachBuffer().
+    // This method behaves like a sequence of detachBuffer() calls.
+    // The return value of the batched method will only be about the
+    // transaction. For a local call, the return value will always be NO_ERROR.
+    virtual status_t detachBuffers(const std::vector<int32_t>& slots,
+                                   std::vector<status_t>* results);
+
+
+    struct AttachBufferOutput : public LightFlattenable<AttachBufferOutput> {
+        AttachBufferOutput() = default;
+
+        // LightFlattenable protocol
+        inline bool isFixedSize() const { return true; }
+        size_t getFlattenedSize() const;
+        status_t flatten(void* buffer, size_t size) const;
+        status_t unflatten(void const* buffer, size_t size);
+
+        status_t result;
+        int slot;
+    };
+    // Batched version of attachBuffer().
+    // This method behaves like a sequence of attachBuffer() calls.
+    // The return value of the batched method will only be about the
+    // transaction. For a local call, the return value will always be NO_ERROR.
+    virtual status_t attachBuffers(
+            const std::vector<sp<GraphicBuffer>>& buffers,
+            std::vector<AttachBufferOutput>* outputs);
+
+    // Batched version of queueBuffer().
+    // This method behaves like a sequence of queueBuffer() calls.
+    // The return value of the batched method will only be about the
+    // transaction. For a local call, the return value will always be NO_ERROR.
+    //
+    // Note: QueueBufferInput::slot was added to QueueBufferInput to include the
+    // `slot` input argument of the non-batched method queueBuffer().
+    virtual status_t queueBuffers(const std::vector<QueueBufferInput>& inputs,
+                                  std::vector<QueueBufferOutput>* outputs);
+
+    struct CancelBufferInput : public Flattenable<CancelBufferInput> {
+        CancelBufferInput() = default;
+
+        // Flattenable protocol
+        static constexpr size_t minFlattenedSize();
+        size_t getFlattenedSize() const;
+        size_t getFdCount() const;
+        status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+        status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+        int slot;
+        sp<Fence> fence;
+    };
+    // Batched version of cancelBuffer().
+    // This method behaves like a sequence of cancelBuffer() calls.
+    // The return value of the batched method will only be about the
+    // transaction. For a local call, the return value will always be NO_ERROR.
+    virtual status_t cancelBuffers(
+            const std::vector<CancelBufferInput>& inputs,
+            std::vector<status_t>* results);
+
+    struct QueryOutput : public LightFlattenable<QueryOutput> {
+        QueryOutput() = default;
+
+        // LightFlattenable protocol
+        inline bool isFixedSize() const { return true; }
+        size_t getFlattenedSize() const;
+        status_t flatten(void* buffer, size_t size) const;
+        status_t unflatten(void const* buffer, size_t size);
+
+        status_t result;
+        int64_t value;
+    };
+    // Batched version of query().
+    // This method behaves like a sequence of query() calls.
+    // The return value of the batched method will only be about the
+    // transaction. For a local call, the return value will always be NO_ERROR.
+    virtual status_t query(const std::vector<int32_t> inputs,
+                           std::vector<QueryOutput>* outputs);
+
 #ifndef NO_BINDER
     // Static method exports any IGraphicBufferProducer object to a parcel. It
     // handles null producer as well.
diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp
index 15bd32d..2af2fe1 100644
--- a/libs/gui/tests/IGraphicBufferProducer_test.cpp
+++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp
@@ -70,6 +70,9 @@
     const int QUEUE_BUFFER_INPUT_SCALING_MODE = 0;
     const int QUEUE_BUFFER_INPUT_TRANSFORM = 0;
     const sp<Fence> QUEUE_BUFFER_INPUT_FENCE = Fence::NO_FENCE;
+    const uint32_t QUEUE_BUFFER_INPUT_STICKY_TRANSFORM = 0;
+    const bool QUEUE_BUFFER_INPUT_GET_TIMESTAMPS = 0;
+    const int QUEUE_BUFFER_INPUT_SLOT = -1;
 
     // Enums to control which IGraphicBufferProducer backend to test.
     enum IGraphicBufferProducerTestCode {
@@ -156,6 +159,9 @@
            scalingMode = QUEUE_BUFFER_INPUT_SCALING_MODE;
            transform = QUEUE_BUFFER_INPUT_TRANSFORM;
            fence = QUEUE_BUFFER_INPUT_FENCE;
+           stickyTransform = QUEUE_BUFFER_INPUT_STICKY_TRANSFORM;
+           getTimestamps = QUEUE_BUFFER_INPUT_GET_TIMESTAMPS;
+           slot = QUEUE_BUFFER_INPUT_SLOT;
         }
 
         IGraphicBufferProducer::QueueBufferInput build() {
@@ -166,7 +172,10 @@
                     crop,
                     scalingMode,
                     transform,
-                    fence);
+                    fence,
+                    stickyTransform,
+                    getTimestamps,
+                    slot);
         }
 
         QueueBufferInputBuilder& setTimestamp(int64_t timestamp) {
@@ -204,6 +213,21 @@
             return *this;
         }
 
+        QueueBufferInputBuilder& setStickyTransform(uint32_t stickyTransform) {
+            this->stickyTransform = stickyTransform;
+            return *this;
+        }
+
+        QueueBufferInputBuilder& setGetTimestamps(bool getTimestamps) {
+            this->getTimestamps = getTimestamps;
+            return *this;
+        }
+
+        QueueBufferInputBuilder& setSlot(int slot) {
+            this->slot = slot;
+            return *this;
+        }
+
     private:
         int64_t timestamp;
         bool isAutoTimestamp;
@@ -212,17 +236,17 @@
         int scalingMode;
         uint32_t transform;
         sp<Fence> fence;
+        uint32_t stickyTransform;
+        bool getTimestamps;
+        int slot;
     }; // struct QueueBufferInputBuilder
 
-    // To easily store dequeueBuffer results into containers
-    struct DequeueBufferResult {
-        int slot;
-        sp<Fence> fence;
-    };
-
-    status_t dequeueBuffer(uint32_t w, uint32_t h, uint32_t format, uint32_t usage, DequeueBufferResult* result) {
-        return mProducer->dequeueBuffer(&result->slot, &result->fence, w, h, format, usage,
-                                        nullptr, nullptr);
+    status_t dequeueBuffer(uint32_t w, uint32_t h, uint32_t format, uint32_t usage,
+                           IGraphicBufferProducer::DequeueBufferOutput* result) {
+        result->result =
+            mProducer->dequeueBuffer(&result->slot, &result->fence, w, h, format, usage,
+                                     &result->bufferAge, nullptr);
+        return result->result;
     }
 
     void setupDequeueRequestBuffer(int *slot, sp<Fence> *fence,
@@ -336,6 +360,27 @@
     EXPECT_OK(mProducer->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &value));
     EXPECT_EQ(DEFAULT_CONSUMER_USAGE_BITS, value);
 
+    { // Test the batched version
+        std::vector<int32_t> inputs = {
+                NATIVE_WINDOW_WIDTH,
+                NATIVE_WINDOW_HEIGHT,
+                NATIVE_WINDOW_FORMAT,
+                NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+                NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND,
+                NATIVE_WINDOW_CONSUMER_USAGE_BITS };
+        using QueryOutput = IGraphicBufferProducer::QueryOutput;
+        std::vector<QueryOutput> outputs;
+        EXPECT_OK(mProducer->query(inputs, &outputs));
+        EXPECT_EQ(DEFAULT_WIDTH, static_cast<uint32_t>(outputs[0].value));
+        EXPECT_EQ(DEFAULT_HEIGHT, static_cast<uint32_t>(outputs[1].value));
+        EXPECT_EQ(DEFAULT_FORMAT, outputs[2].value);
+        EXPECT_LE(0, outputs[3].value);
+        EXPECT_FALSE(outputs[4].value);
+        EXPECT_EQ(DEFAULT_CONSUMER_USAGE_BITS, outputs[5].value);
+        for (const QueryOutput& output : outputs) {
+            EXPECT_OK(output.result);
+        }
+    }
 }
 
 TEST_P(IGraphicBufferProducerTest, Query_ReturnsError) {
@@ -358,6 +403,24 @@
     EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_TRANSFORM_HINT, &value));
     // TODO: Consider documented the above enums as unsupported or make a new enum for IGBP
 
+    { // Test the batched version
+        std::vector<int32_t> inputs = {
+                -1,
+                static_cast<int32_t>(0xDEADBEEF),
+                NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE,
+                NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER,
+                NATIVE_WINDOW_CONCRETE_TYPE,
+                NATIVE_WINDOW_DEFAULT_WIDTH,
+                NATIVE_WINDOW_DEFAULT_HEIGHT,
+                NATIVE_WINDOW_TRANSFORM_HINT};
+        using QueryOutput = IGraphicBufferProducer::QueryOutput;
+        std::vector<QueryOutput> outputs;
+        EXPECT_OK(mProducer->query(inputs, &outputs));
+        for (const QueryOutput& output : outputs) {
+            EXPECT_EQ(BAD_VALUE, output.result);
+        }
+    }
+
     // Value was NULL
     EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_FORMAT, /*value*/nullptr));
 
@@ -416,29 +479,113 @@
 
     // Buffer was not in the dequeued state
     EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+    { // Test batched methods
+        constexpr size_t BATCH_SIZE = 4;
+
+        ASSERT_OK(mProducer->setMaxDequeuedBufferCount(BATCH_SIZE));
+        // Dequeue
+        using DequeueBufferInput = IGraphicBufferProducer::DequeueBufferInput;
+        using DequeueBufferOutput = IGraphicBufferProducer::DequeueBufferOutput;
+        DequeueBufferInput dequeueInput;
+        dequeueInput.width = DEFAULT_WIDTH;
+        dequeueInput.height = DEFAULT_HEIGHT;
+        dequeueInput.format = DEFAULT_FORMAT;
+        dequeueInput.usage = TEST_PRODUCER_USAGE_BITS;
+        dequeueInput.getTimestamps = false;
+        std::vector<DequeueBufferInput> dequeueInputs(BATCH_SIZE, dequeueInput);
+        std::vector<DequeueBufferOutput> dequeueOutputs;
+        EXPECT_OK(mProducer->dequeueBuffers(dequeueInputs, &dequeueOutputs));
+        ASSERT_EQ(dequeueInputs.size(), dequeueOutputs.size());
+
+        // Request
+        std::vector<int32_t> requestInputs;
+        requestInputs.reserve(BATCH_SIZE);
+        for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+            ASSERT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
+                      dequeueOutput.result);
+            requestInputs.emplace_back(dequeueOutput.slot);
+        }
+        using RequestBufferOutput = IGraphicBufferProducer::RequestBufferOutput;
+        std::vector<RequestBufferOutput> requestOutputs;
+        EXPECT_OK(mProducer->requestBuffers(requestInputs, &requestOutputs));
+        ASSERT_EQ(requestInputs.size(), requestOutputs.size());
+        for (const RequestBufferOutput& requestOutput : requestOutputs) {
+            EXPECT_OK(requestOutput.result);
+        }
+
+        // Queue
+        using QueueBufferInput = IGraphicBufferProducer::QueueBufferInput;
+        using QueueBufferOutput = IGraphicBufferProducer::QueueBufferOutput;
+        std::vector<QueueBufferInput> queueInputs;
+        queueInputs.reserve(BATCH_SIZE);
+        for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+            queueInputs.emplace_back(CreateBufferInput()).slot =
+                dequeueOutput.slot;
+        }
+        std::vector<QueueBufferOutput> queueOutputs;
+        EXPECT_OK(mProducer->queueBuffers(queueInputs, &queueOutputs));
+        ASSERT_EQ(queueInputs.size(), queueOutputs.size());
+        for (const QueueBufferOutput& queueOutput : queueOutputs) {
+            EXPECT_OK(queueOutput.result);
+        }
+
+        // Re-queue
+        EXPECT_OK(mProducer->queueBuffers(queueInputs, &queueOutputs));
+        ASSERT_EQ(queueInputs.size(), queueOutputs.size());
+        for (const QueueBufferOutput& queueOutput : queueOutputs) {
+            EXPECT_EQ(BAD_VALUE, queueOutput.result);
+        }
+    }
 }
 
 TEST_P(IGraphicBufferProducerTest, Queue_ReturnsError) {
     ASSERT_NO_FATAL_FAILURE(ConnectProducer());
 
+    using QueueBufferInput = IGraphicBufferProducer::QueueBufferInput;
+    using QueueBufferOutput = IGraphicBufferProducer::QueueBufferOutput;
     // Invalid slot number
     {
         // A generic "valid" input
-        IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-        IGraphicBufferProducer::QueueBufferOutput output;
+        QueueBufferInput input = CreateBufferInput();
+        QueueBufferOutput output;
 
         EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/-1, input, &output));
         EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/0xDEADBEEF, input, &output));
         EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(BufferQueue::NUM_BUFFER_SLOTS,
                                                     input, &output));
+
+        { // Test with the batched version
+            constexpr size_t BATCH_SIZE = 16;
+            input.slot = -1;
+            std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+            std::vector<QueueBufferOutput> outputs;
+            EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+            ASSERT_EQ(inputs.size(), outputs.size());
+            for (const QueueBufferOutput& output : outputs) {
+                EXPECT_EQ(BAD_VALUE, output.result);
+            }
+        }
     }
 
     // Slot was not in the dequeued state (all slots start out in Free state)
     {
-        IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-        IGraphicBufferProducer::QueueBufferOutput output;
+        QueueBufferInput input = CreateBufferInput();
+        QueueBufferOutput output;
 
         EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/0, input, &output));
+
+        { // Test with the batched version
+            constexpr size_t BATCH_SIZE = 16;
+            input.slot = 0;
+            std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+            std::vector<QueueBufferOutput> outputs;
+            EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+            ASSERT_EQ(inputs.size(), outputs.size());
+            for (const QueueBufferOutput& output : outputs) {
+                EXPECT_EQ(BAD_VALUE, output.result);
+            }
+        }
     }
 
     // Put the slot into the "dequeued" state for the rest of the test
@@ -453,10 +600,22 @@
 
     // Slot was enqueued without requesting a buffer
     {
-        IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-        IGraphicBufferProducer::QueueBufferOutput output;
+        QueueBufferInput input = CreateBufferInput();
+        QueueBufferOutput output;
 
         EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+        { // Test with the batched version
+            constexpr size_t BATCH_SIZE = 16;
+            input.slot = dequeuedSlot;
+            std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+            std::vector<QueueBufferOutput> outputs;
+            EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+            ASSERT_EQ(inputs.size(), outputs.size());
+            for (const QueueBufferOutput& output : outputs) {
+                EXPECT_EQ(BAD_VALUE, output.result);
+            }
+        }
     }
 
     // Request the buffer so that the rest of the tests don't fail on earlier checks.
@@ -467,11 +626,23 @@
     {
         sp<Fence> nullFence = nullptr;
 
-        IGraphicBufferProducer::QueueBufferInput input =
+        QueueBufferInput input =
                 QueueBufferInputBuilder().setFence(nullFence).build();
-        IGraphicBufferProducer::QueueBufferOutput output;
+        QueueBufferOutput output;
 
         EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+        { // Test with the batched version
+            constexpr size_t BATCH_SIZE = 16;
+            input.slot = dequeuedSlot;
+            std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+            std::vector<QueueBufferOutput> outputs;
+            EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+            ASSERT_EQ(inputs.size(), outputs.size());
+            for (const QueueBufferOutput& output : outputs) {
+                EXPECT_EQ(BAD_VALUE, output.result);
+            }
+        }
     }
 
     // Scaling mode was unknown
@@ -482,9 +653,33 @@
 
         EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
 
+        { // Test with the batched version
+            constexpr size_t BATCH_SIZE = 16;
+            input.slot = dequeuedSlot;
+            std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+            std::vector<QueueBufferOutput> outputs;
+            EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+            ASSERT_EQ(inputs.size(), outputs.size());
+            for (const QueueBufferOutput& output : outputs) {
+                EXPECT_EQ(BAD_VALUE, output.result);
+            }
+        }
+
         input = QueueBufferInputBuilder().setScalingMode(0xDEADBEEF).build();
 
         EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+        { // Test with the batched version
+            constexpr size_t BATCH_SIZE = 16;
+            input.slot = dequeuedSlot;
+            std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+            std::vector<QueueBufferOutput> outputs;
+            EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+            ASSERT_EQ(inputs.size(), outputs.size());
+            for (const QueueBufferOutput& output : outputs) {
+                EXPECT_EQ(BAD_VALUE, output.result);
+            }
+        }
     }
 
     // Crop rect is out of bounds of the buffer dimensions
@@ -495,6 +690,18 @@
         IGraphicBufferProducer::QueueBufferOutput output;
 
         EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+        { // Test with the batched version
+            constexpr size_t BATCH_SIZE = 16;
+            input.slot = dequeuedSlot;
+            std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+            std::vector<QueueBufferOutput> outputs;
+            EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+            ASSERT_EQ(inputs.size(), outputs.size());
+            for (const QueueBufferOutput& output : outputs) {
+                EXPECT_EQ(BAD_VALUE, output.result);
+            }
+        }
     }
 
     // Abandon the buffer queue so that the last test fails
@@ -507,6 +714,18 @@
 
         // TODO(b/73267953): Make BufferHub honor producer and consumer connection.
         EXPECT_EQ(NO_INIT, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+        { // Test with the batched version
+            constexpr size_t BATCH_SIZE = 16;
+            input.slot = dequeuedSlot;
+            std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+            std::vector<QueueBufferOutput> outputs;
+            EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+            ASSERT_EQ(inputs.size(), outputs.size());
+            for (const QueueBufferOutput& output : outputs) {
+                EXPECT_EQ(NO_INIT, output.result);
+            }
+        }
     }
 }
 
@@ -525,6 +744,44 @@
     // No return code, but at least test that it doesn't blow up...
     // TODO: add a return code
     mProducer->cancelBuffer(dequeuedSlot, dequeuedFence);
+
+    { // Test batched methods
+        constexpr size_t BATCH_SIZE = 4;
+        ASSERT_OK(mProducer->setMaxDequeuedBufferCount(BATCH_SIZE));
+
+        // Dequeue
+        using DequeueBufferInput = IGraphicBufferProducer::DequeueBufferInput;
+        using DequeueBufferOutput = IGraphicBufferProducer::DequeueBufferOutput;
+        DequeueBufferInput dequeueInput;
+        dequeueInput.width = DEFAULT_WIDTH;
+        dequeueInput.height = DEFAULT_HEIGHT;
+        dequeueInput.format = DEFAULT_FORMAT;
+        dequeueInput.usage = TEST_PRODUCER_USAGE_BITS;
+        dequeueInput.getTimestamps = false;
+        std::vector<DequeueBufferInput> dequeueInputs(BATCH_SIZE, dequeueInput);
+        std::vector<DequeueBufferOutput> dequeueOutputs;
+        EXPECT_OK(mProducer->dequeueBuffers(dequeueInputs, &dequeueOutputs));
+        ASSERT_EQ(dequeueInputs.size(), dequeueOutputs.size());
+
+        // Cancel
+        using CancelBufferInput = IGraphicBufferProducer::CancelBufferInput;
+        std::vector<CancelBufferInput> cancelInputs;
+        cancelInputs.reserve(BATCH_SIZE);
+        for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+            ASSERT_EQ(OK,
+                      ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
+                      dequeueOutput.result);
+            CancelBufferInput& cancelInput = cancelInputs.emplace_back();
+            cancelInput.slot = dequeueOutput.slot;
+            cancelInput.fence = dequeueOutput.fence;
+        }
+        std::vector<status_t> cancelOutputs;
+        EXPECT_OK(mProducer->cancelBuffers(cancelInputs, &cancelOutputs));
+        ASSERT_EQ(cancelInputs.size(), cancelOutputs.size());
+        for (status_t result : cancelOutputs) {
+            EXPECT_OK(result);
+        }
+    }
 }
 
 TEST_P(IGraphicBufferProducerTest, SetMaxDequeuedBufferCount_Succeeds) {
@@ -541,11 +798,11 @@
             << "bufferCount: " << minBuffers;
 
     // Should now be able to dequeue up to minBuffers times
-    DequeueBufferResult result;
+    IGraphicBufferProducer::DequeueBufferOutput result;
     for (int i = 0; i < minBuffers; ++i) {
         EXPECT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
                 (dequeueBuffer(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
-                              TEST_PRODUCER_USAGE_BITS, &result)))
+                               TEST_PRODUCER_USAGE_BITS, &result)))
                 << "iteration: " << i << ", slot: " << result.slot;
     }
 
@@ -558,7 +815,6 @@
     ASSERT_OK(mProducer->requestBuffer(result.slot, &buffer));
     ASSERT_OK(mProducer->queueBuffer(result.slot, input, &output));
 
-
     // Should now be able to dequeue up to maxBuffers times
     int dequeuedSlot = -1;
     sp<Fence> dequeuedFence;
@@ -794,6 +1050,71 @@
 
     EXPECT_OK(mProducer->attachBuffer(&slot, buffer));
     EXPECT_OK(buffer->initCheck());
+
+    ASSERT_OK(mProducer->detachBuffer(slot));
+
+    { // Test batched methods
+        constexpr size_t BATCH_SIZE = 4;
+        ASSERT_OK(mProducer->setMaxDequeuedBufferCount(BATCH_SIZE));
+
+        // Dequeue
+        using DequeueBufferInput = IGraphicBufferProducer::DequeueBufferInput;
+        using DequeueBufferOutput = IGraphicBufferProducer::DequeueBufferOutput;
+        DequeueBufferInput dequeueInput;
+        dequeueInput.width = DEFAULT_WIDTH;
+        dequeueInput.height = DEFAULT_HEIGHT;
+        dequeueInput.format = DEFAULT_FORMAT;
+        dequeueInput.usage = TEST_PRODUCER_USAGE_BITS;
+        dequeueInput.getTimestamps = false;
+        std::vector<DequeueBufferInput> dequeueInputs(BATCH_SIZE, dequeueInput);
+        std::vector<DequeueBufferOutput> dequeueOutputs;
+        EXPECT_OK(mProducer->dequeueBuffers(dequeueInputs, &dequeueOutputs));
+        ASSERT_EQ(dequeueInputs.size(), dequeueOutputs.size());
+
+        // Request
+        std::vector<int32_t> requestInputs;
+        requestInputs.reserve(BATCH_SIZE);
+        for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+            ASSERT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
+                      dequeueOutput.result);
+            requestInputs.emplace_back(dequeueOutput.slot);
+        }
+        using RequestBufferOutput = IGraphicBufferProducer::RequestBufferOutput;
+        std::vector<RequestBufferOutput> requestOutputs;
+        EXPECT_OK(mProducer->requestBuffers(requestInputs, &requestOutputs));
+        ASSERT_EQ(requestInputs.size(), requestOutputs.size());
+        for (const RequestBufferOutput& requestOutput : requestOutputs) {
+            EXPECT_OK(requestOutput.result);
+        }
+
+        // Detach
+        std::vector<int32_t> detachInputs;
+        detachInputs.reserve(BATCH_SIZE);
+        for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+            detachInputs.emplace_back(dequeueOutput.slot);
+        }
+        std::vector<status_t> detachOutputs;
+        EXPECT_OK(mProducer->detachBuffers(detachInputs, &detachOutputs));
+        ASSERT_EQ(detachInputs.size(), detachOutputs.size());
+        for (status_t result : detachOutputs) {
+            EXPECT_OK(result);
+        }
+
+        // Attach
+        using AttachBufferOutput = IGraphicBufferProducer::AttachBufferOutput;
+        std::vector<sp<GraphicBuffer>> attachInputs;
+        attachInputs.reserve(BATCH_SIZE);
+        for (const RequestBufferOutput& requestOutput : requestOutputs) {
+            attachInputs.emplace_back(requestOutput.buffer);
+        }
+        std::vector<AttachBufferOutput> attachOutputs;
+        EXPECT_OK(mProducer->attachBuffers(attachInputs, &attachOutputs));
+        ASSERT_EQ(attachInputs.size(), attachOutputs.size());
+        for (const AttachBufferOutput& attachOutput : attachOutputs) {
+            EXPECT_OK(attachOutput.result);
+            EXPECT_NE(-1, attachOutput.slot);
+        }
+    }
 }
 
 #if USE_BUFFER_HUB_AS_BUFFER_QUEUE