Add BufferReleaseChannel

BufferReleaseChannel will be used to communicate buffer releases from SurfaceFlinger to BLASTBufferQueue.

Bug: 294133380
Flag: com.android.graphics.libgui.flags.buffer_release_channel
Test: BufferReleaseChannelTest
Change-Id: Ic38e8eefc96abc0b2bbe780115b7628413e8b829
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 51d2e53..1243b21 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -255,6 +255,7 @@
         "BitTube.cpp",
         "BLASTBufferQueue.cpp",
         "BufferItemConsumer.cpp",
+        "BufferReleaseChannel.cpp",
         "Choreographer.cpp",
         "CompositorTiming.cpp",
         "ConsumerBase.cpp",
diff --git a/libs/gui/BufferReleaseChannel.cpp b/libs/gui/BufferReleaseChannel.cpp
new file mode 100644
index 0000000..27367aa
--- /dev/null
+++ b/libs/gui/BufferReleaseChannel.cpp
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BufferReleaseChannel"
+
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <android-base/result.h>
+#include <android/binder_status.h>
+#include <binder/Parcel.h>
+#include <utils/Flattenable.h>
+
+#include <gui/BufferReleaseChannel.h>
+#include <private/gui/ParcelUtils.h>
+
+using android::base::Result;
+
+namespace android::gui {
+
+namespace {
+
+template <typename T>
+static void readAligned(const void*& buffer, size_t& size, T& value) {
+    size -= FlattenableUtils::align<alignof(T)>(buffer);
+    FlattenableUtils::read(buffer, size, value);
+}
+
+template <typename T>
+static void writeAligned(void*& buffer, size_t& size, T value) {
+    size -= FlattenableUtils::align<alignof(T)>(buffer);
+    FlattenableUtils::write(buffer, size, value);
+}
+
+template <typename T>
+static void addAligned(size_t& size, T /* value */) {
+    size = FlattenableUtils::align<sizeof(T)>(size);
+    size += sizeof(T);
+}
+
+template <typename T>
+static inline constexpr uint32_t low32(const T n) {
+    return static_cast<uint32_t>(static_cast<uint64_t>(n));
+}
+
+template <typename T>
+static inline constexpr uint32_t high32(const T n) {
+    return static_cast<uint32_t>(static_cast<uint64_t>(n) >> 32);
+}
+
+template <typename T>
+static inline constexpr T to64(const uint32_t lo, const uint32_t hi) {
+    return static_cast<T>(static_cast<uint64_t>(hi) << 32 | lo);
+}
+
+} // namespace
+
+size_t BufferReleaseChannel::Message::getPodSize() const {
+    size_t size = 0;
+    addAligned(size, low32(releaseCallbackId.bufferId));
+    addAligned(size, high32(releaseCallbackId.bufferId));
+    addAligned(size, low32(releaseCallbackId.framenumber));
+    addAligned(size, high32(releaseCallbackId.framenumber));
+    addAligned(size, maxAcquiredBufferCount);
+    return size;
+}
+
+size_t BufferReleaseChannel::Message::getFlattenedSize() const {
+    size_t size = releaseFence->getFlattenedSize();
+    size = FlattenableUtils::align<4>(size);
+    size += getPodSize();
+    return size;
+}
+
+status_t BufferReleaseChannel::Message::flatten(void*& buffer, size_t& size, int*& fds,
+                                                size_t& count) const {
+    if (status_t err = releaseFence->flatten(buffer, size, fds, count); err != OK) {
+        return err;
+    }
+    size -= FlattenableUtils::align<4>(buffer);
+
+    // Check we still have enough space
+    if (size < getPodSize()) {
+        return NO_MEMORY;
+    }
+
+    writeAligned(buffer, size, low32(releaseCallbackId.bufferId));
+    writeAligned(buffer, size, high32(releaseCallbackId.bufferId));
+    writeAligned(buffer, size, low32(releaseCallbackId.framenumber));
+    writeAligned(buffer, size, high32(releaseCallbackId.framenumber));
+    writeAligned(buffer, size, maxAcquiredBufferCount);
+    return OK;
+}
+
+status_t BufferReleaseChannel::Message::unflatten(void const*& buffer, size_t& size,
+                                                  int const*& fds, size_t& count) {
+    releaseFence = new Fence();
+    if (status_t err = releaseFence->unflatten(buffer, size, fds, count); err != OK) {
+        return err;
+    }
+    size -= FlattenableUtils::align<4>(buffer);
+
+    // Check we still have enough space
+    if (size < getPodSize()) {
+        return OK;
+    }
+
+    uint32_t bufferIdLo = 0, bufferIdHi = 0;
+    uint32_t frameNumberLo = 0, frameNumberHi = 0;
+
+    readAligned(buffer, size, bufferIdLo);
+    readAligned(buffer, size, bufferIdHi);
+    releaseCallbackId.bufferId = to64<int64_t>(bufferIdLo, bufferIdHi);
+    readAligned(buffer, size, frameNumberLo);
+    readAligned(buffer, size, frameNumberHi);
+    releaseCallbackId.framenumber = to64<uint64_t>(frameNumberLo, frameNumberHi);
+    readAligned(buffer, size, maxAcquiredBufferCount);
+
+    return OK;
+}
+
+status_t BufferReleaseChannel::ConsumerEndpoint::readReleaseFence(
+        ReleaseCallbackId& outReleaseCallbackId, sp<Fence>& outReleaseFence,
+        uint32_t& outMaxAcquiredBufferCount) {
+    Message message;
+    mFlattenedBuffer.resize(message.getFlattenedSize());
+    std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer;
+
+    iovec iov{
+            .iov_base = mFlattenedBuffer.data(),
+            .iov_len = mFlattenedBuffer.size(),
+    };
+
+    msghdr msg{
+            .msg_iov = &iov,
+            .msg_iovlen = 1,
+            .msg_control = controlMessageBuffer.data(),
+            .msg_controllen = controlMessageBuffer.size(),
+    };
+
+    int result;
+    do {
+        result = recvmsg(mFd, &msg, 0);
+    } while (result == -1 && errno == EINTR);
+    if (result == -1) {
+        if (errno == EWOULDBLOCK || errno == EAGAIN) {
+            return WOULD_BLOCK;
+        }
+        ALOGE("Error reading release fence from socket: error %#x (%s)", errno, strerror(errno));
+        return UNKNOWN_ERROR;
+    }
+
+    if (msg.msg_iovlen != 1) {
+        ALOGE("Error reading release fence from socket: bad data length");
+        return UNKNOWN_ERROR;
+    }
+
+    if (msg.msg_controllen % sizeof(int) != 0) {
+        ALOGE("Error reading release fence from socket: bad fd length");
+        return UNKNOWN_ERROR;
+    }
+
+    size_t dataLen = msg.msg_iov->iov_len;
+    const void* data = static_cast<const void*>(msg.msg_iov->iov_base);
+    if (!data) {
+        ALOGE("Error reading release fence from socket: no buffer data");
+        return UNKNOWN_ERROR;
+    }
+
+    size_t fdCount = 0;
+    const int* fdData = nullptr;
+    if (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg)) {
+        fdData = reinterpret_cast<const int*>(CMSG_DATA(cmsg));
+        fdCount = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+    }
+
+    if (status_t err = message.unflatten(data, dataLen, fdData, fdCount); err != OK) {
+        return err;
+    }
+
+    outReleaseCallbackId = message.releaseCallbackId;
+    outReleaseFence = std::move(message.releaseFence);
+    outMaxAcquiredBufferCount = message.maxAcquiredBufferCount;
+
+    return OK;
+}
+
+int BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(const ReleaseCallbackId& callbackId,
+                                                              const sp<Fence>& fence,
+                                                              uint32_t maxAcquiredBufferCount) {
+    Message message{callbackId, fence ? fence : Fence::NO_FENCE, maxAcquiredBufferCount};
+    mFlattenedBuffer.resize(message.getFlattenedSize());
+    int flattenedFd;
+    {
+        // Make copies of needed items since flatten modifies them, and we don't
+        // want to send anything if there's an error during flatten.
+        void* flattenedBufferPtr = mFlattenedBuffer.data();
+        size_t flattenedBufferSize = mFlattenedBuffer.size();
+        int* flattenedFdPtr = &flattenedFd;
+        size_t flattenedFdCount = 1;
+        if (status_t err = message.flatten(flattenedBufferPtr, flattenedBufferSize, flattenedFdPtr,
+                                           flattenedFdCount);
+            err != OK) {
+            ALOGE("Failed to flatten BufferReleaseChannel message.");
+            return err;
+        }
+    }
+
+    iovec iov{
+            .iov_base = mFlattenedBuffer.data(),
+            .iov_len = mFlattenedBuffer.size(),
+    };
+
+    msghdr msg{
+            .msg_iov = &iov,
+            .msg_iovlen = 1,
+    };
+
+    std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer;
+    if (fence && fence->isValid()) {
+        msg.msg_control = controlMessageBuffer.data();
+        msg.msg_controllen = controlMessageBuffer.size();
+
+        cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type = SCM_RIGHTS;
+        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+        memcpy(CMSG_DATA(cmsg), &flattenedFd, sizeof(int));
+    }
+
+    int result;
+    do {
+        result = sendmsg(mFd, &msg, 0);
+    } while (result == -1 && errno == EINTR);
+    if (result == -1) {
+        ALOGD("Error writing release fence to socket: error %#x (%s)", errno, strerror(errno));
+        return -errno;
+    }
+
+    return OK;
+}
+
+status_t BufferReleaseChannel::ProducerEndpoint::readFromParcel(const android::Parcel* parcel) {
+    if (!parcel) return STATUS_BAD_VALUE;
+    SAFE_PARCEL(parcel->readUtf8FromUtf16, &mName);
+    SAFE_PARCEL(parcel->readUniqueFileDescriptor, &mFd);
+    return STATUS_OK;
+}
+
+status_t BufferReleaseChannel::ProducerEndpoint::writeToParcel(android::Parcel* parcel) const {
+    if (!parcel) return STATUS_BAD_VALUE;
+    SAFE_PARCEL(parcel->writeUtf8AsUtf16, mName);
+    SAFE_PARCEL(parcel->writeUniqueFileDescriptor, mFd);
+    return STATUS_OK;
+}
+
+status_t BufferReleaseChannel::open(std::string name,
+                                    std::unique_ptr<ConsumerEndpoint>& outConsumer,
+                                    std::shared_ptr<ProducerEndpoint>& outProducer) {
+    outConsumer.reset();
+    outProducer.reset();
+
+    int sockets[2];
+    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
+        ALOGE("[%s] Failed to create socket pair. errorno=%d message='%s'", name.c_str(), errno,
+              strerror(errno));
+        return -errno;
+    }
+
+    android::base::unique_fd consumerFd(sockets[0]);
+    android::base::unique_fd producerFd(sockets[1]);
+
+    // Socket buffer size. The default is typically about 128KB, which is much larger than
+    // we really need.
+    size_t bufferSize = 32 * 1024;
+    if (setsockopt(consumerFd.get(), SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)) ==
+        -1) {
+        ALOGE("[%s] Failed to set consumer socket send buffer size. errno=%d message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+    if (setsockopt(consumerFd.get(), SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)) ==
+        -1) {
+        ALOGE("[%s] Failed to set consumer socket receive buffer size. errno=%d "
+              "message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+    if (setsockopt(producerFd.get(), SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)) ==
+        -1) {
+        ALOGE("[%s] Failed to set producer socket send buffer size. errno=%d message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+    if (setsockopt(producerFd.get(), SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)) ==
+        -1) {
+        ALOGE("[%s] Failed to set producer socket receive buffer size. errno=%d "
+              "message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+
+    // Configure the consumer socket to be non-blocking.
+    int flags = fcntl(consumerFd.get(), F_GETFL, 0);
+    if (flags == -1) {
+        ALOGE("[%s] Failed to get consumer socket flags. errno=%d message='%s'", name.c_str(),
+              errno, strerror(errno));
+        return -errno;
+    }
+    if (fcntl(consumerFd.get(), F_SETFL, flags | O_NONBLOCK) == -1) {
+        ALOGE("[%s] Failed to set consumer socket to non-blocking mode. errno=%d "
+              "message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+
+    // Configure a timeout for the producer socket.
+    const timeval timeout{.tv_sec = 1, .tv_usec = 0};
+    if (setsockopt(producerFd.get(), SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeval)) == -1) {
+        ALOGE("[%s] Failed to set producer socket timeout. errno=%d message='%s'", name.c_str(),
+              errno, strerror(errno));
+        return -errno;
+    }
+
+    // Make the consumer read-only
+    if (shutdown(consumerFd.get(), SHUT_WR) == -1) {
+        ALOGE("[%s] Failed to shutdown writing on consumer socket. errno=%d message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+
+    // Make the producer write-only
+    if (shutdown(producerFd.get(), SHUT_RD) == -1) {
+        ALOGE("[%s] Failed to shutdown reading on producer socket. errno=%d message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+
+    outConsumer = std::make_unique<ConsumerEndpoint>(name, std::move(consumerFd));
+    outProducer = std::make_shared<ProducerEndpoint>(std::move(name), std::move(producerFd));
+    return STATUS_OK;
+}
+
+} // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 307ae39..0714fd9 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -17,7 +17,6 @@
 #define LOG_TAG "LayerState"
 
 #include <cinttypes>
-#include <cmath>
 
 #include <android/gui/ISurfaceComposerClient.h>
 #include <android/native_window.h>
@@ -194,6 +193,13 @@
     SAFE_PARCEL(output.writeFloat, currentHdrSdrRatio);
     SAFE_PARCEL(output.writeFloat, desiredHdrSdrRatio);
     SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint));
+
+    const bool hasBufferReleaseChannel = (bufferReleaseChannel != nullptr);
+    SAFE_PARCEL(output.writeBool, hasBufferReleaseChannel);
+    if (hasBufferReleaseChannel) {
+        SAFE_PARCEL(output.writeParcelable, *bufferReleaseChannel);
+    }
+
     return NO_ERROR;
 }
 
@@ -339,6 +345,13 @@
     SAFE_PARCEL(input.readInt32, &tmpInt32);
     cachingHint = static_cast<gui::CachingHint>(tmpInt32);
 
+    bool hasBufferReleaseChannel;
+    SAFE_PARCEL(input.readBool, &hasBufferReleaseChannel);
+    if (hasBufferReleaseChannel) {
+        bufferReleaseChannel = std::make_shared<gui::BufferReleaseChannel::ProducerEndpoint>();
+        SAFE_PARCEL(input.readParcelable, bufferReleaseChannel.get());
+    }
+
     return NO_ERROR;
 }
 
@@ -718,6 +731,10 @@
     if (other.what & eFlushJankData) {
         what |= eFlushJankData;
     }
+    if (other.what & eBufferReleaseChannelChanged) {
+        what |= eBufferReleaseChannelChanged;
+        bufferReleaseChannel = other.bufferReleaseChannel;
+    }
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
               "other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64,
@@ -797,6 +814,7 @@
     CHECK_DIFF(diff, eColorChanged, other, color.rgb);
     CHECK_DIFF(diff, eColorSpaceAgnosticChanged, other, colorSpaceAgnostic);
     CHECK_DIFF(diff, eDimmingEnabledChanged, other, dimmingEnabled);
+    if (other.what & eBufferReleaseChannelChanged) diff |= eBufferReleaseChannelChanged;
     return diff;
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index f663600..b5d9366 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2390,6 +2390,22 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBufferReleaseChannel(
+        const sp<SurfaceControl>& sc,
+        const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+
+    s->what |= layer_state_t::eBufferReleaseChannelChanged;
+    s->bufferReleaseChannel = channel;
+
+    registerSurfaceControlForCallback(sc);
+    return *this;
+}
+
 // ---------------------------------------------------------------------------
 
 DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
diff --git a/libs/gui/include/gui/BufferReleaseChannel.h b/libs/gui/include/gui/BufferReleaseChannel.h
new file mode 100644
index 0000000..51fe0b6
--- /dev/null
+++ b/libs/gui/include/gui/BufferReleaseChannel.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 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>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include <binder/Parcelable.h>
+#include <gui/ITransactionCompletedListener.h>
+#include <ui/Fence.h>
+#include <utils/Errors.h>
+
+namespace android::gui {
+
+/**
+ * IPC wrapper to pass release fences from SurfaceFlinger to apps via a local unix domain socket.
+ */
+class BufferReleaseChannel {
+private:
+    class Endpoint {
+    public:
+        Endpoint(std::string name, android::base::unique_fd fd)
+              : mName(std::move(name)), mFd(std::move(fd)) {}
+        Endpoint() {}
+
+        Endpoint(Endpoint&&) noexcept = default;
+        Endpoint& operator=(Endpoint&&) noexcept = default;
+
+        Endpoint(const Endpoint&) = delete;
+        void operator=(const Endpoint&) = delete;
+
+        const android::base::unique_fd& getFd() const { return mFd; }
+
+    protected:
+        std::string mName;
+        android::base::unique_fd mFd;
+    };
+
+public:
+    class ConsumerEndpoint : public Endpoint {
+    public:
+        ConsumerEndpoint(std::string name, android::base::unique_fd fd)
+              : Endpoint(std::move(name), std::move(fd)) {}
+
+        /**
+         * Reads a release fence from the BufferReleaseChannel.
+         *
+         * Returns OK on success.
+         * Returns WOULD_BLOCK if there is no fence present.
+         * Other errors probably indicate that the channel is broken.
+         */
+        status_t readReleaseFence(ReleaseCallbackId& outReleaseCallbackId,
+                                  sp<Fence>& outReleaseFence, uint32_t& maxAcquiredBufferCount);
+
+    private:
+        std::vector<uint8_t> mFlattenedBuffer;
+    };
+
+    class ProducerEndpoint : public Endpoint, public Parcelable {
+    public:
+        ProducerEndpoint(std::string name, android::base::unique_fd fd)
+              : Endpoint(std::move(name), std::move(fd)) {}
+        ProducerEndpoint() {}
+
+        status_t readFromParcel(const android::Parcel* parcel) override;
+        status_t writeToParcel(android::Parcel* parcel) const override;
+
+        status_t writeReleaseFence(const ReleaseCallbackId&, const sp<Fence>& releaseFence,
+                                   uint32_t maxAcquiredBufferCount);
+
+    private:
+        std::vector<uint8_t> mFlattenedBuffer;
+    };
+
+    /**
+     * Create two endpoints that make up the BufferReleaseChannel.
+     *
+     * Return OK on success.
+     */
+    static status_t open(const std::string name, std::unique_ptr<ConsumerEndpoint>& outConsumer,
+                         std::shared_ptr<ProducerEndpoint>& outProducer);
+
+    struct Message : public Flattenable<Message> {
+        ReleaseCallbackId releaseCallbackId;
+        sp<Fence> releaseFence = Fence::NO_FENCE;
+        uint32_t maxAcquiredBufferCount;
+
+        Message() = default;
+        Message(ReleaseCallbackId releaseCallbackId, sp<Fence> releaseFence,
+                uint32_t maxAcquiredBufferCount)
+              : releaseCallbackId{releaseCallbackId},
+                releaseFence{std::move(releaseFence)},
+                maxAcquiredBufferCount{maxAcquiredBufferCount} {}
+
+        // Flattenable protocol
+        size_t getFlattenedSize() const;
+
+        size_t getFdCount() const { return releaseFence->getFdCount(); }
+
+        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);
+
+    private:
+        size_t getPodSize() const;
+    };
+};
+
+} // namespace android::gui
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 3fb1894..d419945 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -34,6 +34,7 @@
 #include <android/gui/TrustedOverlay.h>
 
 #include <ftl/flags.h>
+#include <gui/BufferReleaseChannel.h>
 #include <gui/DisplayCaptureArgs.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/LayerCaptureArgs.h>
@@ -220,6 +221,7 @@
         eDropInputModeChanged = 0x8000'00000000,
         eExtendedRangeBrightnessChanged = 0x10000'00000000,
         eEdgeExtensionChanged = 0x20000'00000000,
+        eBufferReleaseChannelChanged = 0x40000'00000000,
     };
 
     layer_state_t();
@@ -412,6 +414,8 @@
 
     TrustedPresentationThresholds trustedPresentationThresholds;
     TrustedPresentationListener trustedPresentationListener;
+
+    std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> bufferReleaseChannel;
 };
 
 class ComposerState {
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 61a65bb..4f9af16 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -45,6 +45,7 @@
 #include <android/gui/BnJankListener.h>
 #include <android/gui/ISurfaceComposerClient.h>
 
+#include <gui/BufferReleaseChannel.h>
 #include <gui/CpuConsumer.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/ITransactionCompletedListener.h>
@@ -762,6 +763,10 @@
                                          const Rect& destinationFrame);
         Transaction& setDropInputMode(const sp<SurfaceControl>& sc, gui::DropInputMode mode);
 
+        Transaction& setBufferReleaseChannel(
+                const sp<SurfaceControl>& sc,
+                const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel);
+
         status_t setDisplaySurface(const sp<IBinder>& token,
                 const sp<IGraphicBufferProducer>& bufferProducer);
 
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index b342a7d..9558eda 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -33,6 +33,7 @@
         "BLASTBufferQueue_test.cpp",
         "BufferItemConsumer_test.cpp",
         "BufferQueue_test.cpp",
+        "BufferReleaseChannel_test.cpp",
         "Choreographer_test.cpp",
         "CompositorTiming_test.cpp",
         "CpuConsumer_test.cpp",
diff --git a/libs/gui/tests/BufferReleaseChannel_test.cpp b/libs/gui/tests/BufferReleaseChannel_test.cpp
new file mode 100644
index 0000000..11d122b
--- /dev/null
+++ b/libs/gui/tests/BufferReleaseChannel_test.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <gui/BufferReleaseChannel.h>
+
+using namespace std::string_literals;
+using android::gui::BufferReleaseChannel;
+
+namespace android {
+
+namespace {
+
+// Helper function to check if two file descriptors point to the same file.
+bool is_same_file(int fd1, int fd2) {
+    struct stat stat1;
+    if (fstat(fd1, &stat1) != 0) {
+        return false;
+    }
+    struct stat stat2;
+    if (fstat(fd2, &stat2) != 0) {
+        return false;
+    }
+    return (stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
+}
+
+} // namespace
+
+TEST(BufferReleaseChannelTest, MessageFlattenable) {
+    ReleaseCallbackId releaseCallbackId{1, 2};
+    sp<Fence> releaseFence = sp<Fence>::make(memfd_create("fake-fence-fd", 0));
+    uint32_t maxAcquiredBufferCount = 5;
+
+    std::vector<uint8_t> dataBuffer;
+    std::vector<int> fdBuffer;
+
+    // Verify that we can flatten a message
+    {
+        BufferReleaseChannel::Message message{releaseCallbackId, releaseFence,
+                                              maxAcquiredBufferCount};
+
+        dataBuffer.resize(message.getFlattenedSize());
+        void* dataPtr = dataBuffer.data();
+        size_t dataSize = dataBuffer.size();
+
+        fdBuffer.resize(message.getFdCount());
+        int* fdPtr = fdBuffer.data();
+        size_t fdSize = fdBuffer.size();
+
+        ASSERT_EQ(OK, message.flatten(dataPtr, dataSize, fdPtr, fdSize));
+
+        // Fence's unique_fd uses fdsan to check ownership of the file descriptor. Normally the file
+        // descriptor is passed through the Unix socket and duplicated (and sent to another process)
+        // so there's no problem with duplicate file descriptor ownership. For this unit test, we
+        // need to set up a duplicate file descriptor to avoid crashing due to duplicate ownership.
+        ASSERT_EQ(releaseFence->get(), fdBuffer[0]);
+        fdBuffer[0] = message.releaseFence->dup();
+    }
+
+    // Verify that we can unflatten a message
+    {
+        BufferReleaseChannel::Message message;
+
+        const void* dataPtr = dataBuffer.data();
+        size_t dataSize = dataBuffer.size();
+
+        const int* fdPtr = fdBuffer.data();
+        size_t fdSize = fdBuffer.size();
+
+        ASSERT_EQ(OK, message.unflatten(dataPtr, dataSize, fdPtr, fdSize));
+        ASSERT_EQ(releaseCallbackId, message.releaseCallbackId);
+        ASSERT_TRUE(is_same_file(releaseFence->get(), message.releaseFence->get()));
+        ASSERT_EQ(maxAcquiredBufferCount, message.maxAcquiredBufferCount);
+    }
+}
+
+// Verify that the BufferReleaseChannel consume returns WOULD_BLOCK when there's no message
+// available.
+TEST(BufferReleaseChannelTest, ConsumerEndpointIsNonBlocking) {
+    std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer;
+    std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer;
+    ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer));
+
+    ReleaseCallbackId releaseCallbackId;
+    sp<Fence> releaseFence;
+    uint32_t maxAcquiredBufferCount;
+    ASSERT_EQ(WOULD_BLOCK,
+              consumer->readReleaseFence(releaseCallbackId, releaseFence, maxAcquiredBufferCount));
+}
+
+// Verify that we can write a message to the BufferReleaseChannel producer and read that message
+// using the BufferReleaseChannel consumer.
+TEST(BufferReleaseChannelTest, ProduceAndConsume) {
+    std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer;
+    std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer;
+    ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer));
+
+    sp<Fence> fence = sp<Fence>::make(memfd_create("fake-fence-fd", 0));
+
+    for (uint64_t i = 0; i < 64; i++) {
+        ReleaseCallbackId producerId{i, i + 1};
+        uint32_t maxAcquiredBufferCount = i + 2;
+        ASSERT_EQ(OK, producer->writeReleaseFence(producerId, fence, maxAcquiredBufferCount));
+    }
+
+    for (uint64_t i = 0; i < 64; i++) {
+        ReleaseCallbackId expectedId{i, i + 1};
+        uint32_t expectedMaxAcquiredBufferCount = i + 2;
+
+        ReleaseCallbackId consumerId;
+        sp<Fence> consumerFence;
+        uint32_t maxAcquiredBufferCount;
+        ASSERT_EQ(OK,
+                  consumer->readReleaseFence(consumerId, consumerFence, maxAcquiredBufferCount));
+
+        ASSERT_EQ(expectedId, consumerId);
+        ASSERT_TRUE(is_same_file(fence->get(), consumerFence->get()));
+        ASSERT_EQ(expectedMaxAcquiredBufferCount, maxAcquiredBufferCount);
+    }
+}
+
+} // namespace android
\ No newline at end of file