Add allocator-v2 extended options to BufferQueue

Bug: 268382490
Test: libgui_test
Change-Id: If43b31eede87cddfcbfe4b24b53c1bafb453ebf2
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index fb69fda..69345a9 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -423,6 +423,11 @@
     sp<IConsumerListener> listener;
     bool callOnFrameDequeued = false;
     uint64_t bufferId = 0; // Only used if callOnFrameDequeued == true
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    std::vector<gui::AdditionalOptions> allocOptions;
+    uint32_t allocOptionsGenId = 0;
+#endif
+
     { // Autolock scope
         std::unique_lock<std::mutex> lock(mCore->mMutex);
 
@@ -486,11 +491,17 @@
         }
 
         const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
-        if (mCore->mSharedBufferSlot == found &&
-                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) {
-            BQ_LOGE("dequeueBuffer: cannot re-allocate a shared"
-                    "buffer");
 
+        bool needsReallocation = buffer == nullptr ||
+                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        needsReallocation |= mSlots[found].mAdditionalOptionsGenerationId !=
+                mCore->mAdditionalOptionsGenerationId;
+#endif
+
+        if (mCore->mSharedBufferSlot == found && needsReallocation) {
+            BQ_LOGE("dequeueBuffer: cannot re-allocate a shared buffer");
             return BAD_VALUE;
         }
 
@@ -505,9 +516,7 @@
 
         mSlots[found].mBufferState.dequeue();
 
-        if ((buffer == nullptr) ||
-                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage))
-        {
+        if (needsReallocation) {
             if (CC_UNLIKELY(ATRACE_ENABLED())) {
                 if (buffer == nullptr) {
                     ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.c_str());
@@ -530,6 +539,10 @@
             mSlots[found].mFence = Fence::NO_FENCE;
             mCore->mBufferAge = 0;
             mCore->mIsAllocating = true;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptions = mCore->mAdditionalOptions;
+            allocOptionsGenId = mCore->mAdditionalOptionsGenerationId;
+#endif
 
             returnFlags |= BUFFER_NEEDS_REALLOCATION;
         } else {
@@ -575,9 +588,29 @@
 
     if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
         BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<GraphicBufferAllocator::AdditionalOptions> tempOptions;
+        tempOptions.reserve(allocOptions.size());
+        for (const auto& it : allocOptions) {
+            tempOptions.emplace_back(it.name.c_str(), it.value);
+        }
+        const GraphicBufferAllocator::AllocationRequest allocRequest = {
+                .importBuffer = true,
+                .width = width,
+                .height = height,
+                .format = format,
+                .layerCount = BQ_LAYER_COUNT,
+                .usage = usage,
+                .requestorName = {mConsumerName.c_str(), mConsumerName.size()},
+                .extras = std::move(tempOptions),
+        };
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+#else
         sp<GraphicBuffer> graphicBuffer =
                 new GraphicBuffer(width, height, format, BQ_LAYER_COUNT, usage,
                                   {mConsumerName.c_str(), mConsumerName.size()});
+#endif
 
         status_t error = graphicBuffer->initCheck();
 
@@ -587,6 +620,9 @@
             if (error == NO_ERROR && !mCore->mIsAbandoned) {
                 graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
                 mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                mSlots[*outSlot].mAdditionalOptionsGenerationId = allocOptionsGenId;
+#endif
                 callOnFrameDequeued = true;
                 bufferId = mSlots[*outSlot].mGraphicBuffer->getId();
             }
@@ -1342,6 +1378,9 @@
     }
 
     mCore->mAllowAllocation = true;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    mCore->mAdditionalOptions.clear();
+#endif
     VALIDATE_CONSISTENCY();
     return status;
 }
@@ -1410,6 +1449,9 @@
                     mCore->mSidebandStream.clear();
                     mCore->mDequeueCondition.notify_all();
                     mCore->mAutoPrerotation = false;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                    mCore->mAdditionalOptions.clear();
+#endif
                     listener = mCore->mConsumerListener;
                 } else if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
                     BQ_LOGE("disconnect: not connected (req=%d)", api);
@@ -1462,6 +1504,10 @@
         PixelFormat allocFormat = PIXEL_FORMAT_UNKNOWN;
         uint64_t allocUsage = 0;
         std::string allocName;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<gui::AdditionalOptions> allocOptions;
+        uint32_t allocOptionsGenId = 0;
+#endif
         { // Autolock scope
             std::unique_lock<std::mutex> lock(mCore->mMutex);
             mCore->waitWhileAllocatingLocked(lock);
@@ -1490,14 +1536,42 @@
             allocUsage = usage | mCore->mConsumerUsageBits;
             allocName.assign(mCore->mConsumerName.c_str(), mCore->mConsumerName.size());
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptions = mCore->mAdditionalOptions;
+            allocOptionsGenId = mCore->mAdditionalOptionsGenerationId;
+#endif
+
             mCore->mIsAllocating = true;
+
         } // Autolock scope
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<GraphicBufferAllocator::AdditionalOptions> tempOptions;
+        tempOptions.reserve(allocOptions.size());
+        for (const auto& it : allocOptions) {
+            tempOptions.emplace_back(it.name.c_str(), it.value);
+        }
+        const GraphicBufferAllocator::AllocationRequest allocRequest = {
+                .importBuffer = true,
+                .width = allocWidth,
+                .height = allocHeight,
+                .format = allocFormat,
+                .layerCount = BQ_LAYER_COUNT,
+                .usage = allocUsage,
+                .requestorName = allocName,
+                .extras = std::move(tempOptions),
+        };
+#endif
+
         Vector<sp<GraphicBuffer>> buffers;
         for (size_t i = 0; i < newBufferCount; ++i) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+#else
             sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
                     allocWidth, allocHeight, allocFormat, BQ_LAYER_COUNT,
                     allocUsage, allocName);
+#endif
 
             status_t result = graphicBuffer->initCheck();
 
@@ -1524,8 +1598,12 @@
             PixelFormat checkFormat = format != 0 ?
                     format : mCore->mDefaultBufferFormat;
             uint64_t checkUsage = usage | mCore->mConsumerUsageBits;
+            bool allocOptionsChanged = false;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptionsChanged = allocOptionsGenId != mCore->mAdditionalOptionsGenerationId;
+#endif
             if (checkWidth != allocWidth || checkHeight != allocHeight ||
-                checkFormat != allocFormat || checkUsage != allocUsage) {
+                checkFormat != allocFormat || checkUsage != allocUsage || allocOptionsChanged) {
                 // Something changed while we released the lock. Retry.
                 BQ_LOGV("allocateBuffers: size/format/usage changed while allocating. Retrying.");
                 mCore->mIsAllocating = false;
@@ -1543,6 +1621,9 @@
                 mCore->clearBufferSlotLocked(*slot); // Clean up the slot first
                 mSlots[*slot].mGraphicBuffer = buffers[i];
                 mSlots[*slot].mFence = Fence::NO_FENCE;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                mSlots[*slot].mAdditionalOptionsGenerationId = allocOptionsGenId;
+#endif
 
                 // freeBufferLocked puts this slot on the free slots list. Since
                 // we then attached a buffer, move the slot to free buffer list.
@@ -1778,4 +1859,29 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t BufferQueueProducer::setAdditionalOptions(
+        const std::vector<gui::AdditionalOptions>& options) {
+    ATRACE_CALL();
+    BQ_LOGV("setAdditionalOptions, size = %zu", options.size());
+
+    if (!GraphicBufferAllocator::get().supportsAdditionalOptions()) {
+        return INVALID_OPERATION;
+    }
+
+    std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+    if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
+        BQ_LOGE("setAdditionalOptions: BufferQueue not connected, cannot set additional options");
+        return NO_INIT;
+    }
+
+    if (mCore->mAdditionalOptions != options) {
+        mCore->mAdditionalOptions = options;
+        mCore->mAdditionalOptionsGenerationId++;
+    }
+    return NO_ERROR;
+}
+#endif
+
 } // namespace android
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index e81c098..0914480 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -80,6 +80,7 @@
     QUERY_MULTIPLE,
     GET_LAST_QUEUED_BUFFER2,
     SET_FRAME_RATE,
+    SET_ADDITIONAL_OPTIONS,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -778,6 +779,25 @@
         return result;
     }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    virtual status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        if (options.size() > 100) {
+            return BAD_VALUE;
+        }
+        data.writeInt32(options.size());
+        for (const auto& it : options) {
+            data.writeCString(it.name.c_str());
+            data.writeInt64(it.value);
+        }
+        status_t result = remote()->transact(SET_ADDITIONAL_OPTIONS, data, &reply);
+        if (result == NO_ERROR) {
+            result = reply.readInt32();
+        }
+        return result;
+    }
+#endif
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -981,6 +1001,13 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t IGraphicBufferProducer::setAdditionalOptions(const std::vector<gui::AdditionalOptions>&) {
+    // No-op for IGBP other than BufferQueue.
+    return INVALID_OPERATION;
+}
+#endif
+
 status_t IGraphicBufferProducer::exportToParcel(Parcel* parcel) {
     status_t res = OK;
     res = parcel->writeUint32(USE_BUFFER_QUEUE);
@@ -1533,6 +1560,28 @@
             return NO_ERROR;
         }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        case SET_ADDITIONAL_OPTIONS: {
+            CHECK_INTERFACE(IGraphicBuffer, data, reply);
+            int optionCount = data.readInt32();
+            if (optionCount < 0 || optionCount > 100) {
+                return BAD_VALUE;
+            }
+            std::vector<gui::AdditionalOptions> opts;
+            opts.reserve(optionCount);
+            for (int i = 0; i < optionCount; i++) {
+                const char* name = data.readCString();
+                int64_t value = 0;
+                if (name == nullptr || data.readInt64(&value) != NO_ERROR) {
+                    return BAD_VALUE;
+                }
+                opts.emplace_back(name, value);
+            }
+            status_t result = setAdditionalOptions(opts);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
+#endif
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 086544e..87fd448 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1475,6 +1475,9 @@
     case NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO:
         res = dispatchSetFrameTimelineInfo(args);
         break;
+    case NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS:
+        res = dispatchSetAdditionalOptions(args);
+        break;
     default:
         res = NAME_NOT_FOUND;
         break;
@@ -1833,6 +1836,24 @@
     return setFrameTimelineInfo(nativeWindowFtlInfo.frameNumber, ftlInfo);
 }
 
+int Surface::dispatchSetAdditionalOptions(va_list args) {
+    ATRACE_CALL();
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    const AHardwareBufferLongOptions* opts = va_arg(args, const AHardwareBufferLongOptions*);
+    const size_t optsSize = va_arg(args, size_t);
+    std::vector<gui::AdditionalOptions> convertedOpts;
+    convertedOpts.reserve(optsSize);
+    for (size_t i = 0; i < optsSize; i++) {
+        convertedOpts.emplace_back(opts[i].name, opts[i].value);
+    }
+    return setAdditionalOptions(convertedOpts);
+#else
+    (void)args;
+    return INVALID_OPERATION;
+#endif
+}
+
 bool Surface::transformToDisplayInverse() const {
     return (mTransform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) ==
             NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
@@ -2619,6 +2640,17 @@
     return BAD_VALUE;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t Surface::setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) {
+    if (!GraphicBufferAllocator::get().supportsAdditionalOptions()) {
+        return INVALID_OPERATION;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    return mGraphicBufferProducer->setAdditionalOptions(options);
+}
+#endif
+
 sp<IBinder> Surface::getSurfaceControlHandle() const {
     Mutex::Autolock lock(mMutex);
     return mSurfaceControlHandle;
diff --git a/libs/gui/include/gui/AdditionalOptions.h b/libs/gui/include/gui/AdditionalOptions.h
new file mode 100644
index 0000000..87cb913
--- /dev/null
+++ b/libs/gui/include/gui/AdditionalOptions.h
@@ -0,0 +1,32 @@
+/*
+ * 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 <string>
+
+namespace android::gui {
+// Additional options to pass to AHardwareBuffer_allocateWithOptions.
+// See also allocator-v2's BufferDescriptorInfo.aidl
+struct AdditionalOptions {
+    std::string name;
+    int64_t value;
+
+    bool operator==(const AdditionalOptions& other) const {
+        return value == other.value && name == other.name;
+    }
+};
+} // namespace android::gui
diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h
index 22c2be7..bb52c8e 100644
--- a/libs/gui/include/gui/BufferQueueCore.h
+++ b/libs/gui/include/gui/BufferQueueCore.h
@@ -17,6 +17,9 @@
 #ifndef ANDROID_GUI_BUFFERQUEUECORE_H
 #define ANDROID_GUI_BUFFERQUEUECORE_H
 
+#include <com_android_graphics_libgui_flags.h>
+
+#include <gui/AdditionalOptions.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/BufferSlot.h>
@@ -357,6 +360,14 @@
     // This allows the consumer to acquire an additional buffer if that buffer is not droppable and
     // will eventually be released or acquired by the consumer.
     bool mAllowExtraAcquire = false;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    // Additional options to pass when allocating GraphicBuffers.
+    // GenerationID changes when the options change, indicating reallocation is required
+    uint32_t mAdditionalOptionsGenerationId = 0;
+    std::vector<gui::AdditionalOptions> mAdditionalOptions;
+#endif
+
 }; // class BufferQueueCore
 
 } // namespace android
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index de47483..37a9607 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 #define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 
+#include <gui/AdditionalOptions.h>
 #include <gui/BufferQueueDefs.h>
 
 #include <gui/IGraphicBufferProducer.h>
@@ -208,6 +209,10 @@
                           int8_t changeFrameRateStrategy) override;
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) override;
+#endif
+
 protected:
     // see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the
     // total maximum buffer count for the buffer queue (dequeued AND acquired)
diff --git a/libs/gui/include/gui/BufferSlot.h b/libs/gui/include/gui/BufferSlot.h
index 57704b1..5b32710 100644
--- a/libs/gui/include/gui/BufferSlot.h
+++ b/libs/gui/include/gui/BufferSlot.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_GUI_BUFFERSLOT_H
 #define ANDROID_GUI_BUFFERSLOT_H
 
+#include <com_android_graphics_libgui_flags.h>
+
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
 
@@ -230,6 +232,11 @@
     // producer. If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when
     // dequeued to prevent the producer from using a stale cached buffer.
     bool mNeedsReallocation;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    // The generation id of the additional options that mGraphicBuffer was allocated with
+    uint32_t mAdditionalOptionsGenerationId = 0;
+#endif
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 7639e70..8fca946 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -31,6 +31,7 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+#include <gui/AdditionalOptions.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/HdrMetadata.h>
 
@@ -684,6 +685,10 @@
                                   int8_t changeFrameRateStrategy);
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    virtual status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options);
+#endif
+
     struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
         RequestBufferOutput() = default;
 
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 39a59e4..bdcaaf2 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -215,6 +215,16 @@
                                   int8_t changeFrameRateStrategy);
     virtual status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    /**
+     * Set additional options to be passed when allocating a buffer. Only valid if IAllocator-V2
+     * or newer is available, otherwise will return INVALID_OPERATION. Only allowed to be called
+     * after connect and options are cleared when disconnect happens. Returns NO_INIT if not
+     * connected
+     */
+    status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options);
+#endif
+
 protected:
     virtual ~Surface();
 
@@ -302,6 +312,7 @@
     int dispatchGetLastQueuedBuffer(va_list args);
     int dispatchGetLastQueuedBuffer2(va_list args);
     int dispatchSetFrameTimelineInfo(va_list args);
+    int dispatchSetAdditionalOptions(va_list args);
 
     std::mutex mNameMutex;
     std::string mName;
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index 3864699..a902a8c 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -16,3 +16,11 @@
   bug: "310927247"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "bq_extendedallocate"
+  namespace: "core_graphics"
+  description: "Add BQ support for allocate with extended options"
+  bug: "268382490"
+  is_fixed_read_only: true
+}
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 2faa330..ea8acbb 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -24,6 +24,7 @@
         "-Wextra",
         "-Wthread-safety",
         "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_SETFRAMERATE=true",
+        "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_EXTENDEDALLOCATE=true",
     ],
 
     srcs: [
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 1ec6f91..272c5ed 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -28,6 +28,8 @@
 
 #include <ui/GraphicBuffer.h>
 
+#include <android-base/properties.h>
+
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
@@ -47,6 +49,10 @@
 
 using namespace std::chrono_literals;
 
+static bool IsCuttlefish() {
+    return ::android::base::GetProperty("ro.product.board", "") == "cutf";
+}
+
 namespace android {
 using namespace com::android::graphics::libgui;
 
@@ -1439,4 +1445,55 @@
     EXPECT_EQ(nullptr, bufferConsumer.get());
 }
 
+TEST_F(BufferQueueTest, TestAdditionalOptions) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<BufferItemConsumer> bufferConsumer =
+            sp<BufferItemConsumer>::make(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 2);
+    ASSERT_NE(nullptr, bufferConsumer.get());
+    sp<Surface> surface = sp<Surface>::make(producer);
+    native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
+    native_window_set_buffers_dimensions(surface.get(), 100, 100);
+
+    std::array<AHardwareBufferLongOptions, 1> extras = {{
+            {.name = "android.hardware.graphics.common.Dataspace", ADATASPACE_DISPLAY_P3},
+    }};
+
+    ASSERT_EQ(NO_INIT,
+              native_window_set_buffers_additional_options(surface.get(), extras.data(),
+                                                           extras.size()));
+
+    if (!IsCuttlefish()) {
+        GTEST_SKIP() << "Not cuttlefish";
+    }
+
+    ASSERT_EQ(OK, native_window_api_connect(surface.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(OK,
+              native_window_set_buffers_additional_options(surface.get(), extras.data(),
+                                                           extras.size()));
+
+    ANativeWindowBuffer* windowBuffer = nullptr;
+    int fence = -1;
+    ASSERT_EQ(OK, ANativeWindow_dequeueBuffer(surface.get(), &windowBuffer, &fence));
+
+    AHardwareBuffer* buffer = ANativeWindowBuffer_getHardwareBuffer(windowBuffer);
+    ASSERT_TRUE(buffer);
+    ADataSpace dataSpace = AHardwareBuffer_getDataSpace(buffer);
+    EXPECT_EQ(ADATASPACE_DISPLAY_P3, dataSpace);
+
+    ANativeWindow_cancelBuffer(surface.get(), windowBuffer, -1);
+
+    // Check that reconnecting properly clears the options
+    ASSERT_EQ(OK, native_window_api_disconnect(surface.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(OK, native_window_api_connect(surface.get(), NATIVE_WINDOW_API_CPU));
+
+    ASSERT_EQ(OK, ANativeWindow_dequeueBuffer(surface.get(), &windowBuffer, &fence));
+    buffer = ANativeWindowBuffer_getHardwareBuffer(windowBuffer);
+    ASSERT_TRUE(buffer);
+    dataSpace = AHardwareBuffer_getDataSpace(buffer);
+    EXPECT_EQ(ADATASPACE_UNKNOWN, dataSpace);
+}
+
 } // namespace android
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 969a5cf..33c303a 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -41,6 +41,8 @@
 #include <system/graphics.h>
 #include <unistd.h>
 
+#include <vndk/hardware_buffer.h>
+
 // system/window.h is a superset of the vndk and apex apis
 #include <apex/window.h>
 #include <vndk/window.h>
@@ -257,6 +259,7 @@
     NATIVE_WINDOW_SET_QUERY_INTERCEPTOR           = 47,    /* private */
     NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO         = 48,    /* private */
     NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2         = 49,    /* private */
+    NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS  = 50,
     // clang-format on
 };
 
@@ -1182,6 +1185,26 @@
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineInfo);
 }
 
+/**
+ * native_window_set_buffers_additional_options(..., ExtendableType* additionalOptions, size_t size)
+ * All buffers dequeued after this call will have the additionalOptions specified.
+ *
+ * This must only be called after api_connect, otherwise NO_INIT is returned. The options are
+ * cleared in api_disconnect & api_connect
+ *
+ * If IAllocator is not v2 or newer this method returns INVALID_OPERATION
+ *
+ * \return NO_ERROR on success.
+ * \return NO_INIT if no api is connected
+ * \return INVALID_OPERATION if additional option support is not available
+ */
+static inline int native_window_set_buffers_additional_options(
+        struct ANativeWindow* window, const AHardwareBufferLongOptions* additionalOptions,
+        size_t additionalOptionsSize) {
+    return window->perform(window, NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS, additionalOptions,
+                           additionalOptionsSize);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Candidates for APEX visibility
 // These functions are planned to be made stable for APEX modules, but have not
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index 98082fb..1ebe597 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -291,5 +291,9 @@
     return NO_ERROR;
 }
 
+bool GraphicBufferAllocator::supportsAdditionalOptions() const {
+    return mAllocator->supportsAdditionalOptions();
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index e6015e0..4167dcb 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -226,6 +226,8 @@
             const GraphicBufferAllocator::AllocationRequest&) const {
         return GraphicBufferAllocator::AllocationResult(UNKNOWN_TRANSACTION);
     }
+
+    virtual bool supportsAdditionalOptions() const { return false; }
 };
 
 } // namespace android
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
index f9e8f5e..5aa5019 100644
--- a/libs/ui/include/ui/Gralloc5.h
+++ b/libs/ui/include/ui/Gralloc5.h
@@ -178,6 +178,8 @@
     [[nodiscard]] GraphicBufferAllocator::AllocationResult allocate(
             const GraphicBufferAllocator::AllocationRequest&) const override;
 
+    bool supportsAdditionalOptions() const override { return true; }
+
 private:
     const Gralloc5Mapper &mMapper;
     std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator;
diff --git a/libs/ui/include/ui/GraphicBufferAllocator.h b/libs/ui/include/ui/GraphicBufferAllocator.h
index 8f461e1..bbb2d77 100644
--- a/libs/ui/include/ui/GraphicBufferAllocator.h
+++ b/libs/ui/include/ui/GraphicBufferAllocator.h
@@ -107,6 +107,8 @@
     void dump(std::string& res, bool less = true) const;
     static void dumpToSystemLog(bool less = true);
 
+    bool supportsAdditionalOptions() const;
+
 protected:
     struct alloc_rec_t {
         uint32_t width;