Merge changes from topic "multiple-picture-listeners" into main

* changes:
  Add additional ActivePictureTracker test coverage and rename tests
  Support multiple active picture listeners
  Rename of ActivePictureUpdater to ActivePictureTracker
diff --git a/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp b/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp
index b1c6940..50ea0c7 100644
--- a/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp
+++ b/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp
@@ -47,6 +47,8 @@
 } // namespace android
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // TODO(b/183141167): need to rewrite 'dump' to avoid SIGPIPE.
+    signal(SIGPIPE, SIG_IGN);
     auto service = sp<InstalldNativeService>::make();
     fuzzService(service, FuzzedDataProvider(data, size));
     return 0;
diff --git a/cmds/servicemanager/Access.cpp b/cmds/servicemanager/Access.cpp
index 8098724..6e2abf6 100644
--- a/cmds/servicemanager/Access.cpp
+++ b/cmds/servicemanager/Access.cpp
@@ -34,7 +34,9 @@
 
 #ifdef __ANDROID__
 static std::string getPidcon(pid_t pid) {
-    android_errorWriteLog(0x534e4554, "121035042");
+    CHECK_EQ(nullptr, IPCThreadState::self()->getServingStackPointer())
+            << "Did not get context from PID " << pid
+            << ". We should always get contexts from other processes.";
 
     char* lookup = nullptr;
     if (getpidcon(pid, &lookup) < 0) {
diff --git a/cmds/servicemanager/main.cpp b/cmds/servicemanager/main.cpp
index c126e91..df5a8ed 100644
--- a/cmds/servicemanager/main.cpp
+++ b/cmds/servicemanager/main.cpp
@@ -165,6 +165,7 @@
     IPCThreadState::self()->disableBackgroundScheduling(true);
 
     sp<ServiceManager> manager = sp<ServiceManager>::make(std::make_unique<Access>());
+    manager->setRequestingSid(true);
     if (!manager->addService("manager", manager, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
         LOG(ERROR) << "Could not self register servicemanager";
     }
diff --git a/include/android/looper.h b/include/android/looper.h
index 8cf1396..409db84 100644
--- a/include/android/looper.h
+++ b/include/android/looper.h
@@ -106,7 +106,7 @@
     /**
      * Result from ALooper_pollOnce() and ALooper_pollAll():
      * An error occurred. The poll may also have been explicitly woken by
-     * ALooper_wake(()).
+     * ALooper_wake().
      */
     ALOOPER_POLL_ERROR = -4,
 };
@@ -224,11 +224,11 @@
  * hidden and callers should migrate to ALooper_pollOnce. Binary compatibility
  * is preserved to support already-compiled apps.
  *
- * \bug ALooper_pollAll will not wake in response to ALooper_wake calls if it
+ * \bug ALooper_pollAll() will not wake in response to ALooper_wake() calls if it
  * also handles another event at the same time.
  *
- * \deprecated Calls to ALooper_pollAll should be replaced with
- * ALooper_pollOnce. If you call ALooper_pollOnce in a loop, you *must* treat
+ * \deprecated Calls to ALooper_pollAll() should be replaced with
+ * ALooper_pollOnce(). If you call ALooper_pollOnce() in a loop, you *must* treat
  * all return values as if they also indicate ALOOPER_POLL_WAKE.
  */
 int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData)
@@ -242,7 +242,7 @@
  * This method can be called on any thread.
  * This method returns immediately.
  *
- * \bug ALooper_pollAll will not reliably wake in response to ALooper_wake.
+ * \bug ALooper_pollAll() will not reliably wake in response to ALooper_wake().
  */
 void ALooper_wake(ALooper* looper);
 
diff --git a/libs/binder/BackendUnifiedServiceManager.cpp b/libs/binder/BackendUnifiedServiceManager.cpp
index 03d687a..34d5a09 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -132,6 +132,7 @@
               serviceName.c_str());
         return false;
     }
+    if (kRemoveStaticList) return true;
     for (const char* name : kStaticCachableList) {
         if (name == serviceName) {
             return true;
@@ -175,7 +176,7 @@
                                       "isBinderAlive_false");
     }
     // If we reach here with kRemoveStaticList=true then we know service isn't lazy
-    else if (kRemoveStaticList || mCacheForGetService->isClientSideCachingEnabled(serviceName)) {
+    else if (mCacheForGetService->isClientSideCachingEnabled(serviceName)) {
         binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
                                       "BinderCacheWithInvalidation::updateCache successful");
         return mCacheForGetService->setItem(serviceName, binder);
diff --git a/libs/binder/binder_module.h b/libs/binder/binder_module.h
index 65cdcd7..b3a2d9e 100644
--- a/libs/binder/binder_module.h
+++ b/libs/binder/binder_module.h
@@ -32,34 +32,4 @@
 #include <linux/android/binder.h>
 #include <sys/ioctl.h>
 
-struct binder_frozen_state_info {
-    binder_uintptr_t cookie;
-    __u32 is_frozen;
-};
-
-#ifndef BR_FROZEN_BINDER
-// Temporary definition of BR_FROZEN_BINDER until UAPI binder.h includes it.
-#define BR_FROZEN_BINDER _IOR('r', 21, struct binder_frozen_state_info)
-#endif // BR_FROZEN_BINDER
-
-#ifndef BR_CLEAR_FREEZE_NOTIFICATION_DONE
-// Temporary definition of BR_CLEAR_FREEZE_NOTIFICATION_DONE until UAPI binder.h includes it.
-#define BR_CLEAR_FREEZE_NOTIFICATION_DONE _IOR('r', 22, binder_uintptr_t)
-#endif // BR_CLEAR_FREEZE_NOTIFICATION_DONE
-
-#ifndef BC_REQUEST_FREEZE_NOTIFICATION
-// Temporary definition of BC_REQUEST_FREEZE_NOTIFICATION until UAPI binder.h includes it.
-#define BC_REQUEST_FREEZE_NOTIFICATION _IOW('c', 19, struct binder_handle_cookie)
-#endif // BC_REQUEST_FREEZE_NOTIFICATION
-
-#ifndef BC_CLEAR_FREEZE_NOTIFICATION
-// Temporary definition of BC_CLEAR_FREEZE_NOTIFICATION until UAPI binder.h includes it.
-#define BC_CLEAR_FREEZE_NOTIFICATION _IOW('c', 20, struct binder_handle_cookie)
-#endif // BC_CLEAR_FREEZE_NOTIFICATION
-
-#ifndef BC_FREEZE_NOTIFICATION_DONE
-// Temporary definition of BC_FREEZE_NOTIFICATION_DONE until UAPI binder.h includes it.
-#define BC_FREEZE_NOTIFICATION_DONE _IOW('c', 21, binder_uintptr_t)
-#endif // BC_FREEZE_NOTIFICATION_DONE
-
 #endif // _BINDER_MODULE_H_
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index 30e005c..5924a2d 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -220,8 +220,6 @@
         "android.app.IActivityManager",
         "android.app.IUidObserver",
         "android.drm.IDrm",
-        "android.dvr.IVsyncCallback",
-        "android.dvr.IVsyncService",
         "android.gfx.tests.ICallback",
         "android.gfx.tests.IIPCTest",
         "android.gfx.tests.ISafeInterfaceTest",
@@ -264,17 +262,12 @@
         "android.media.IResourceManagerService",
         "android.os.IComplexTypeInterface",
         "android.os.IPermissionController",
-        "android.os.IPingResponder",
         "android.os.IProcessInfoService",
         "android.os.ISchedulingPolicyService",
-        "android.os.IStringConstants",
         "android.os.storage.IObbActionListener",
         "android.os.storage.IStorageEventListener",
         "android.os.storage.IStorageManager",
         "android.os.storage.IStorageShutdownObserver",
-        "android.service.vr.IPersistentVrStateCallbacks",
-        "android.service.vr.IVrManager",
-        "android.service.vr.IVrStateCallbacks",
         "android.ui.ISurfaceComposer",
         "android.utils.IMemory",
         "android.utils.IMemoryHeap",
@@ -286,9 +279,6 @@
         "com.android.internal.os.IShellCallback",
         "drm.IDrmManagerService",
         "drm.IDrmServiceListener",
-        "IAAudioClient",
-        "IAAudioService",
-        "VtsFuzzer",
         nullptr,
 };
 
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 9e92f95..c038c95 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -43,6 +43,7 @@
 #include <input/BlockingQueue.h>
 #include <processgroup/processgroup.h>
 #include <utils/Flattenable.h>
+#include <utils/SystemClock.h>
 
 #include <linux/sched.h>
 #include <sys/epoll.h>
@@ -343,7 +344,12 @@
         }
 
         bool checkFreezeSupport() {
-            std::ifstream freezer_file("/sys/fs/cgroup/uid_0/cgroup.freeze");
+            std::string path;
+            if (!CgroupGetAttributePathForTask("FreezerState", getpid(), &path)) {
+                return false;
+            }
+
+            std::ifstream freezer_file(path);
             // Pass test on devices where the cgroup v2 freezer is not supported
             if (freezer_file.fail()) {
                 return false;
@@ -1837,14 +1843,6 @@
     EXPECT_TRUE(reply.readBool());
 }
 
-size_t epochMillis() {
-    using std::chrono::duration_cast;
-    using std::chrono::milliseconds;
-    using std::chrono::seconds;
-    using std::chrono::system_clock;
-    return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
-}
-
 TEST_F(BinderLibTest, HangingServices) {
     Parcel data, reply;
     sp<IBinder> server = addServer();
@@ -1853,7 +1851,7 @@
     data.writeInt32(delay);
     // b/266537959 - must take before taking lock, since countdown is started in the remote
     // process there.
-    size_t epochMsBefore = epochMillis();
+    int64_t timeBefore = uptimeMillis();
     EXPECT_THAT(server->transact(BINDER_LIB_TEST_PROCESS_TEMPORARY_LOCK, data, &reply), NO_ERROR);
     std::vector<std::thread> ts;
     for (size_t i = 0; i < kKernelThreads + 1; i++) {
@@ -1867,10 +1865,10 @@
     for (auto &t : ts) {
         t.join();
     }
-    size_t epochMsAfter = epochMillis();
+    int64_t timeAfter = uptimeMillis();
 
     // deadlock occurred and threads only finished after 1s passed.
-    EXPECT_GE(epochMsAfter, epochMsBefore + delay);
+    EXPECT_GE(timeAfter, timeBefore + delay);
 }
 
 TEST_F(BinderLibTest, BinderProxyCount) {
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 7aee903..38465b0 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -1032,7 +1032,7 @@
     std::lock_guard _lock{mMutex};
     if (mLastAcquiredFrameNumber >= frameNumber) {
         // Apply the transaction since we have already acquired the desired frame.
-        t->apply();
+        t->setApplyToken(mApplyToken).apply();
     } else {
         mPendingTransactions.emplace_back(frameNumber, *t);
         // Clear the transaction so it can't be applied elsewhere.
@@ -1232,6 +1232,11 @@
             return OK;
         }
 
+        // Provide a callback for Choreographer to start buffer stuffing recovery when blocked
+        // on buffer release.
+        std::function<void()> callbackCopy = bbq->getWaitForBufferReleaseCallback();
+        if (callbackCopy) callbackCopy();
+
         // BufferQueue has already checked if we have a free buffer. If there's an unread interrupt,
         // we want to ignore it. This must be done before unlocking the BufferQueue lock to ensure
         // we don't miss an interrupt.
@@ -1344,6 +1349,16 @@
     mApplyToken = std::move(applyToken);
 }
 
+void BLASTBufferQueue::setWaitForBufferReleaseCallback(std::function<void()> callback) {
+    std::lock_guard _lock{mWaitForBufferReleaseMutex};
+    mWaitForBufferReleaseCallback = std::move(callback);
+}
+
+std::function<void()> BLASTBufferQueue::getWaitForBufferReleaseCallback() const {
+    std::lock_guard _lock{mWaitForBufferReleaseMutex};
+    return mWaitForBufferReleaseCallback;
+}
+
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
 
 void BLASTBufferQueue::updateBufferReleaseProducer() {
diff --git a/libs/gui/Flags.cpp b/libs/gui/Flags.cpp
index 85ee2cd..ee2802f 100644
--- a/libs/gui/Flags.cpp
+++ b/libs/gui/Flags.cpp
@@ -29,6 +29,14 @@
 #endif
 }
 
+ParcelableSurfaceType surfaceToParcelableSurfaceType(const sp<Surface>& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    return view::Surface::fromSurface(surface);
+#else
+    return surface->getIGraphicBufferProducer();
+#endif
+}
+
 sp<IGraphicBufferProducer> surfaceTypeToIGBP(const sp<SurfaceType>& surface) {
 #if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
     return surface->getIGraphicBufferProducer();
diff --git a/libs/gui/aidl/android/gui/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl
index 4920344..2bbed2b 100644
--- a/libs/gui/aidl/android/gui/CaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -69,10 +69,5 @@
     // exact colorspace is not an appropriate intermediate result.
     // Note that if the caller is requesting a specific dataspace, this hint does nothing.
     boolean hintForSeamlessTransition = false;
-
-    // Allows the screenshot to attach a gainmap, which allows for a per-pixel
-    // transformation of the screenshot to another luminance range, typically
-    // mapping an SDR base image into HDR.
-    boolean attachGainmap = false;
 }
 
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 07558aa..1bc1dd0 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -144,6 +144,12 @@
      */
     void setTransactionHangCallback(std::function<void(const std::string&)> callback);
     void setApplyToken(sp<IBinder>);
+
+    void setWaitForBufferReleaseCallback(std::function<void()> callback)
+            EXCLUDES(mWaitForBufferReleaseMutex);
+    std::function<void()> getWaitForBufferReleaseCallback() const
+            EXCLUDES(mWaitForBufferReleaseMutex);
+
     virtual ~BLASTBufferQueue();
 
     void onFirstRef() override;
@@ -186,6 +192,7 @@
     sp<SurfaceControl> mSurfaceControl GUARDED_BY(mMutex);
 
     mutable std::mutex mMutex;
+    mutable std::mutex mWaitForBufferReleaseMutex;
     std::condition_variable mCallbackCV;
 
     // BufferQueue internally allows 1 more than
@@ -324,6 +331,7 @@
 
     std::unordered_set<uint64_t> mSyncedFrameNumbers GUARDED_BY(mMutex);
 
+    std::function<void()> mWaitForBufferReleaseCallback GUARDED_BY(mWaitForBufferReleaseMutex);
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
     // BufferReleaseChannel is used to communicate buffer releases from SurfaceFlinger to the
     // client.
diff --git a/libs/gui/include/gui/Flags.h b/libs/gui/include/gui/Flags.h
index 845bc54..446841b 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/libs/gui/include/gui/Flags.h
@@ -46,6 +46,7 @@
 
 namespace flagtools {
 sp<SurfaceType> surfaceToSurfaceType(const sp<Surface>& surface);
+ParcelableSurfaceType surfaceToParcelableSurfaceType(const sp<Surface>& surface);
 ParcelableSurfaceType toParcelableSurfaceType(const view::Surface& surface);
 sp<IGraphicBufferProducer> surfaceTypeToIGBP(const sp<SurfaceType>& surface);
 bool isSurfaceTypeValid(const sp<SurfaceType>& surface);
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 45f41f1..06f00a4 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -34,6 +34,8 @@
 #include <binder/Parcel.h>
 #include <binder/ProcessState.h>
 
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferConsumer.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
@@ -1225,9 +1227,17 @@
         sp<IGraphicBufferConsumer> consumer;
         sp<IGraphicBufferProducer> producer;
         BufferQueue::createBufferQueue(&producer, &consumer);
-        consumer->setConsumerName(String8("Virtual disp consumer"));
+        consumer->setConsumerName(String8("Virtual disp consumer (MultiDisplayTests)"));
         consumer->setDefaultBufferSize(width, height);
-        mProducers.push_back(producer);
+
+        class StubConsumerListener : public BnConsumerListener {
+            virtual void onFrameAvailable(const BufferItem&) override {}
+            virtual void onBuffersReleased() override {}
+            virtual void onSidebandStreamChanged() override {}
+        };
+
+        consumer->consumerConnect(sp<StubConsumerListener>::make(), true);
+        mBufferQueues.push_back({consumer, producer});
 
         std::string name = "VirtualDisplay";
         name += std::to_string(mVirtualDisplays.size());
@@ -1244,7 +1254,7 @@
     }
 
     std::vector<sp<IBinder>> mVirtualDisplays;
-    std::vector<sp<IGraphicBufferProducer>> mProducers;
+    std::vector<std::tuple<sp<IGraphicBufferConsumer>, sp<IGraphicBufferProducer>>> mBufferQueues;
 };
 
 TEST_F(MultiDisplayTests, drop_touch_if_layer_on_invalid_display) {
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index ca41346..3205c32 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -112,6 +112,10 @@
         static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
                 AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
         "HAL and AHardwareBuffer pixel format don't match");
+static_assert(
+        static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::YCBCR_P210) ==
+                AHARDWAREBUFFER_FORMAT_YCbCr_P210,
+        "HAL and AHardwareBuffer pixel format don't match");
 
 static enum AHardwareBufferStatus filterStatus(status_t status) {
     switch (status) {
@@ -300,8 +304,10 @@
       if (result == 0) {
         outPlanes->planeCount = 3;
         outPlanes->planes[0].data = yuvData.y;
-        // P010 is word-aligned 10-bit semiplaner, and YCbCr_422_I is a single interleaved plane
+        // P010 & P210 are word-aligned 10-bit semiplaner, and YCbCr_422_I is a single interleaved
+        // plane
         if (format == AHARDWAREBUFFER_FORMAT_YCbCr_P010 ||
+            format == AHARDWAREBUFFER_FORMAT_YCbCr_P210 ||
             format == AHARDWAREBUFFER_FORMAT_YCbCr_422_I) {
             outPlanes->planes[0].pixelStride = 2;
         } else {
@@ -724,6 +730,7 @@
         case AHARDWAREBUFFER_FORMAT_YCrCb_420_SP:
         case AHARDWAREBUFFER_FORMAT_YCbCr_422_I:
         case AHARDWAREBUFFER_FORMAT_YCbCr_P010:
+        case AHARDWAREBUFFER_FORMAT_YCbCr_P210:
             return true;
         default:
             return false;
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 907590a..873fc67 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -107,16 +107,15 @@
     return resultFuture;
 }
 
-ftl::Future<FenceResult> RenderEngine::drawGainmap(
-        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+ftl::Future<FenceResult> RenderEngine::tonemapAndDrawGainmap(
         const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
-        float hdrSdrRatio, ui::Dataspace dataspace,
+        float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
         const std::shared_ptr<ExternalTexture>& gainmap) {
     const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
     std::future<FenceResult> resultFuture = resultPromise->get_future();
     updateProtectedContext({}, {sdr.get(), hdr.get(), gainmap.get()});
-    drawGainmapInternal(std::move(resultPromise), sdr, std::move(sdrFence), hdr,
-                        std::move(hdrFence), hdrSdrRatio, dataspace, gainmap);
+    tonemapAndDrawGainmapInternal(std::move(resultPromise), hdr, std::move(hdrFence), hdrSdrRatio,
+                                  dataspace, sdr, gainmap);
     return resultFuture;
 }
 
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 95c4d03..c2dd4ae 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -217,12 +217,17 @@
                                                 const std::shared_ptr<ExternalTexture>& buffer,
                                                 base::unique_fd&& bufferFence);
 
-    virtual ftl::Future<FenceResult> drawGainmap(const std::shared_ptr<ExternalTexture>& sdr,
-                                                 base::borrowed_fd&& sdrFence,
-                                                 const std::shared_ptr<ExternalTexture>& hdr,
-                                                 base::borrowed_fd&& hdrFence, float hdrSdrRatio,
-                                                 ui::Dataspace dataspace,
-                                                 const std::shared_ptr<ExternalTexture>& gainmap);
+    // Tonemaps an HDR input image and draws an SDR rendition, plus a gainmap
+    // describing how to recover the HDR image.
+    //
+    // The HDR input image is ALWAYS encoded with an sRGB transfer function and
+    // is a floating point format. Accordingly, the hdrSdrRatio describes the
+    // max luminance in the HDR input image above SDR, and the dataspace
+    // describes the input primaries.
+    virtual ftl::Future<FenceResult> tonemapAndDrawGainmap(
+            const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+            float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
+            const std::shared_ptr<ExternalTexture>& gainmap);
 
     // Clean-up method that should be called on the main thread after the
     // drawFence returned by drawLayers fires. This method will free up
@@ -310,11 +315,10 @@
             const DisplaySettings& display, const std::vector<LayerSettings>& layers,
             const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) = 0;
 
-    virtual void drawGainmapInternal(
+    virtual void tonemapAndDrawGainmapInternal(
             const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
-            const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
             const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
-            float hdrSdrRatio, ui::Dataspace dataspace,
+            float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
             const std::shared_ptr<ExternalTexture>& gainmap) = 0;
 };
 
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index fb8331d..c42e403 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -46,17 +46,16 @@
                  ftl::Future<FenceResult>(const DisplaySettings&, const std::vector<LayerSettings>&,
                                           const std::shared_ptr<ExternalTexture>&,
                                           base::unique_fd&&));
-    MOCK_METHOD7(drawGainmap,
+    MOCK_METHOD6(tonemapAndDrawGainmap,
                  ftl::Future<FenceResult>(const std::shared_ptr<ExternalTexture>&,
-                                          base::borrowed_fd&&,
-                                          const std::shared_ptr<ExternalTexture>&,
                                           base::borrowed_fd&&, float, ui::Dataspace,
+                                          const std::shared_ptr<ExternalTexture>&,
                                           const std::shared_ptr<ExternalTexture>&));
-    MOCK_METHOD8(drawGainmapInternal,
+    MOCK_METHOD7(tonemapAndDrawGainmapInternal,
                  void(const std::shared_ptr<std::promise<FenceResult>>&&,
-                      const std::shared_ptr<ExternalTexture>&, base::borrowed_fd&&,
                       const std::shared_ptr<ExternalTexture>&, base::borrowed_fd&&, float,
-                      ui::Dataspace, const std::shared_ptr<ExternalTexture>&));
+                      ui::Dataspace, const std::shared_ptr<ExternalTexture>&,
+                      const std::shared_ptr<ExternalTexture>&));
     MOCK_METHOD5(drawLayersInternal,
                  void(const std::shared_ptr<std::promise<FenceResult>>&&, const DisplaySettings&,
                       const std::vector<LayerSettings>&, const std::shared_ptr<ExternalTexture>&,
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index b3284e4..7e8ccef 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -567,9 +567,7 @@
         if (usingLocalTonemap) {
             const float inputRatio =
                     hdrType == HdrRenderType::GENERIC_HDR ? 1.0f : parameters.layerDimmingRatio;
-            static MouriMap kMapper;
-            shader = kMapper.mouriMap(getActiveContext(), shader, inputRatio,
-                                      parameters.display.targetHdrSdrRatio);
+            shader = localTonemap(shader, inputRatio, parameters.display.targetHdrSdrRatio);
         }
 
         // disable tonemapping if we already locally tonemapped
@@ -610,6 +608,12 @@
     return shader;
 }
 
+sk_sp<SkShader> SkiaRenderEngine::localTonemap(sk_sp<SkShader> shader, float inputMultiplier,
+                                               float targetHdrSdrRatio) {
+    static MouriMap kMapper;
+    return kMapper.mouriMap(getActiveContext(), shader, inputMultiplier, targetHdrSdrRatio);
+}
+
 void SkiaRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
     if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
         // Record display settings when capture is running.
@@ -1009,7 +1013,7 @@
             const auto& item = layer.source.buffer;
             auto imageTextureRef = getOrCreateBackendTexture(item.buffer->getBuffer(), false);
 
-            // if the layer's buffer has a fence, then we must must respect the fence prior to using
+            // if the layer's buffer has a fence, then we must respect the fence prior to using
             // the buffer.
             if (layer.source.buffer.fence != nullptr) {
                 waitFence(context, layer.source.buffer.fence->get());
@@ -1212,44 +1216,58 @@
     resultPromise->set_value(std::move(drawFence));
 }
 
-void SkiaRenderEngine::drawGainmapInternal(
+void SkiaRenderEngine::tonemapAndDrawGainmapInternal(
         const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
-        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
         const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
-        float hdrSdrRatio, ui::Dataspace dataspace,
+        float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
         const std::shared_ptr<ExternalTexture>& gainmap) {
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     auto context = getActiveContext();
-    auto surfaceTextureRef = getOrCreateBackendTexture(gainmap->getBuffer(), true);
-    sk_sp<SkSurface> dstSurface =
-            surfaceTextureRef->getOrCreateSurface(ui::Dataspace::V0_SRGB_LINEAR);
+    auto gainmapTextureRef = getOrCreateBackendTexture(gainmap->getBuffer(), true);
+    sk_sp<SkSurface> gainmapSurface =
+            gainmapTextureRef->getOrCreateSurface(ui::Dataspace::V0_SRGB_LINEAR);
 
-    waitFence(context, sdrFence);
-    const auto sdrTextureRef = getOrCreateBackendTexture(sdr->getBuffer(), false);
-    const auto sdrImage = sdrTextureRef->makeImage(dataspace, kPremul_SkAlphaType);
-    const auto sdrShader =
-            sdrImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
-                                 SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
-                                 nullptr);
+    auto sdrTextureRef = getOrCreateBackendTexture(sdr->getBuffer(), true);
+    sk_sp<SkSurface> sdrSurface = sdrTextureRef->getOrCreateSurface(dataspace);
+
     waitFence(context, hdrFence);
     const auto hdrTextureRef = getOrCreateBackendTexture(hdr->getBuffer(), false);
     const auto hdrImage = hdrTextureRef->makeImage(dataspace, kPremul_SkAlphaType);
     const auto hdrShader =
             hdrImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
-                                 SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
+                                 SkSamplingOptions({SkFilterMode::kNearest, SkMipmapMode::kNone}),
                                  nullptr);
 
+    const auto tonemappedShader = localTonemap(hdrShader, 1.0f, 1.0f);
+
     static GainmapFactory kGainmapFactory;
-    const auto gainmapShader = kGainmapFactory.createSkShader(sdrShader, hdrShader, hdrSdrRatio);
+    const auto gainmapShader =
+            kGainmapFactory.createSkShader(tonemappedShader, hdrShader, hdrSdrRatio);
 
-    const auto canvas = dstSurface->getCanvas();
-    SkPaint paint;
-    paint.setShader(gainmapShader);
-    paint.setBlendMode(SkBlendMode::kSrc);
-    canvas->drawPaint(paint);
+    sp<Fence> drawFence;
 
-    auto drawFence = sp<Fence>::make(flushAndSubmit(context, dstSurface));
-    trace(drawFence);
+    {
+        const auto canvas = sdrSurface->getCanvas();
+        SkPaint paint;
+        paint.setShader(tonemappedShader);
+        paint.setBlendMode(SkBlendMode::kSrc);
+        canvas->drawPaint(paint);
+
+        drawFence = sp<Fence>::make(flushAndSubmit(context, sdrSurface));
+        trace(drawFence);
+    }
+
+    {
+        const auto canvas = gainmapSurface->getCanvas();
+        SkPaint paint;
+        paint.setShader(gainmapShader);
+        paint.setBlendMode(SkBlendMode::kSrc);
+        canvas->drawPaint(paint);
+
+        auto gmFence = sp<Fence>::make(flushAndSubmit(context, gainmapSurface));
+        trace(gmFence);
+        drawFence = Fence::merge("gm-ss", drawFence, gmFence);
+    }
     resultPromise->set_value(std::move(drawFence));
 }
 
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 7be4c25..92b7af9 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -143,13 +143,11 @@
                             const std::vector<LayerSettings>& layers,
                             const std::shared_ptr<ExternalTexture>& buffer,
                             base::unique_fd&& bufferFence) override final;
-    void drawGainmapInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
-                             const std::shared_ptr<ExternalTexture>& sdr,
-                             base::borrowed_fd&& sdrFence,
-                             const std::shared_ptr<ExternalTexture>& hdr,
-                             base::borrowed_fd&& hdrFence, float hdrSdrRatio,
-                             ui::Dataspace dataspace,
-                             const std::shared_ptr<ExternalTexture>& gainmap) override final;
+    void tonemapAndDrawGainmapInternal(
+            const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+            const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+            float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
+            const std::shared_ptr<ExternalTexture>& gainmap) override final;
 
     void dump(std::string& result) override final;
 
@@ -168,6 +166,8 @@
     };
     sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
 
+    sk_sp<SkShader> localTonemap(sk_sp<SkShader>, float inputMultiplier, float targetHdrSdrRatio);
+
     const PixelFormat mDefaultPixelFormat;
 
     // Identifier used for various mappings of layers to various
diff --git a/libs/renderengine/skia/filters/LutShader.cpp b/libs/renderengine/skia/filters/LutShader.cpp
index 5e9dfbb..8fc3286 100644
--- a/libs/renderengine/skia/filters/LutShader.cpp
+++ b/libs/renderengine/skia/filters/LutShader.cpp
@@ -39,10 +39,13 @@
     uniform int key;
     uniform int dimension;
     uniform vec3 luminanceCoefficients; // for CIE_Y
+    // for hlg/pq transfer function, we need normalize it to [0.0, 1.0]
+    // we use `normalizeScalar` to do so
+    uniform float normalizeScalar;
 
     vec4 main(vec2 xy) {
         float4 rgba = image.eval(xy);
-        float3 linear = toLinearSrgb(rgba.rgb);
+        float3 linear = toLinearSrgb(rgba.rgb) * normalizeScalar;
         if (dimension == 1) {
             // RGB
             if (key == 0) {
@@ -52,19 +55,19 @@
                 float gainR = lut.eval(vec2(indexR, 0.0) + 0.5).r;
                 float gainG = lut.eval(vec2(indexG, 0.0) + 0.5).r;
                 float gainB = lut.eval(vec2(indexB, 0.0) + 0.5).r;
-                return float4(linear.r * gainR, linear.g * gainG, linear.b * gainB, rgba.a);
+                linear = float3(linear.r * gainR, linear.g * gainG, linear.b * gainB);
             // MAX_RGB
             } else if (key == 1) {
                 float maxRGB = max(linear.r, max(linear.g, linear.b));
                 float index = maxRGB * float(size - 1);
                 float gain = lut.eval(vec2(index, 0.0) + 0.5).r;
-                return float4(linear * gain, rgba.a);
+                linear = linear * gain;
             // CIE_Y
             } else if (key == 2) {
                 float y = dot(linear, luminanceCoefficients) / 3.0;
                 float index = y * float(size - 1);
                 float gain = lut.eval(vec2(index, 0.0) + 0.5).r;
-                return float4(linear * gain, rgba.a);
+                linear = linear * gain;
             }
         } else if (dimension == 3) {
             if (key == 0) {
@@ -110,12 +113,10 @@
                 float3 c0 = mix(c00, c10, linear.g);
                 float3 c1 = mix(c01, c11, linear.g);
 
-                float3 val = mix(c0, c1, linear.b);
-
-                return float4(val, rgba.a);
+                linear = mix(c0, c1, linear.b);
             }
         }
-        return rgba;
+        return float4(linear, rgba.a);
     })");
 
 // same as shader::toColorSpace function
@@ -197,9 +198,22 @@
                                             ? SkSamplingOptions(SkFilterMode::kLinear)
                                             : SkSamplingOptions());
 
+    float normalizeScalar = 1.0;
+    switch (srcDataspace & HAL_DATASPACE_TRANSFER_MASK) {
+        case HAL_DATASPACE_TRANSFER_HLG:
+            normalizeScalar = 0.203;
+            break;
+        case HAL_DATASPACE_TRANSFER_ST2084:
+            normalizeScalar = 0.0203;
+            break;
+        default:
+            normalizeScalar = 1.0;
+    }
     const int uSize = static_cast<int>(size);
     const int uKey = static_cast<int>(samplingKey);
     const int uDimension = static_cast<int>(dimension);
+    const float uNormalizeScalar = static_cast<float>(normalizeScalar);
+
     if (static_cast<LutProperties::SamplingKey>(samplingKey) == LutProperties::SamplingKey::CIE_Y) {
         // Use predefined colorspaces of input dataspace so that we can get D65 illuminant
         mat3 toXYZMatrix(toColorSpace(srcDataspace).getRGBtoXYZ());
@@ -211,6 +225,7 @@
     mBuilder->uniform("size") = uSize;
     mBuilder->uniform("key") = uKey;
     mBuilder->uniform("dimension") = uDimension;
+    mBuilder->uniform("normalizeScalar") = uNormalizeScalar;
     return mBuilder->makeShader();
 }
 
diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp
index b099bcf..aa12cef 100644
--- a/libs/renderengine/skia/filters/MouriMap.cpp
+++ b/libs/renderengine/skia/filters/MouriMap.cpp
@@ -30,12 +30,12 @@
 }
 const SkString kCrosstalkAndChunk16x16(R"(
     uniform shader bitmap;
-    uniform float hdrSdrRatio;
+    uniform float inputMultiplier;
     vec4 main(vec2 xy) {
         float maximum = 0.0;
         for (int y = 0; y < 16; y++) {
             for (int x = 0; x < 16; x++) {
-                float3 linear = toLinearSrgb(bitmap.eval((xy - 0.5) * 16 + 0.5 + vec2(x, y)).rgb) * hdrSdrRatio;
+                float3 linear = toLinearSrgb(bitmap.eval((xy - 0.5) * 16 + 0.5 + vec2(x, y)).rgb) * inputMultiplier;
                 float maxRGB = max(linear.r, max(linear.g, linear.b));
                 maximum = max(maximum, log2(max(maxRGB, 1.0)));
             }
@@ -77,12 +77,12 @@
     uniform shader image;
     uniform shader lux;
     uniform float scaleFactor;
-    uniform float hdrSdrRatio;
+    uniform float inputMultiplier;
     uniform float targetHdrSdrRatio;
     vec4 main(vec2 xy) {
         float localMax = lux.eval(xy * scaleFactor).r;
         float4 rgba = image.eval(xy);
-        float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio;
+        float3 linear = toLinearSrgb(rgba.rgb) * inputMultiplier;
 
         if (localMax <= targetHdrSdrRatio) {
             return float4(fromLinearSrgb(linear), rgba.a);
@@ -116,19 +116,19 @@
         mTonemap(makeEffect(kTonemap)) {}
 
 sk_sp<SkShader> MouriMap::mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input,
-                                   float hdrSdrRatio, float targetHdrSdrRatio) {
-    auto downchunked = downchunk(context, input, hdrSdrRatio);
+                                   float inputMultiplier, float targetHdrSdrRatio) {
+    auto downchunked = downchunk(context, input, inputMultiplier);
     auto localLux = blur(context, downchunked.get());
-    return tonemap(input, localLux.get(), hdrSdrRatio, targetHdrSdrRatio);
+    return tonemap(input, localLux.get(), inputMultiplier, targetHdrSdrRatio);
 }
 
 sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
-                                   float hdrSdrRatio) const {
+                                   float inputMultiplier) const {
     SkMatrix matrix;
     SkImage* image = input->isAImage(&matrix, (SkTileMode*)nullptr);
     SkRuntimeShaderBuilder crosstalkAndChunk16x16Builder(mCrosstalkAndChunk16x16);
     crosstalkAndChunk16x16Builder.child("bitmap") = input;
-    crosstalkAndChunk16x16Builder.uniform("hdrSdrRatio") = hdrSdrRatio;
+    crosstalkAndChunk16x16Builder.uniform("inputMultiplier") = inputMultiplier;
     // TODO: fp16 might be overkill. Most practical surfaces use 8-bit RGB for HDR UI and 10-bit YUV
     // for HDR video. These downsample operations compute log2(max(linear RGB, 1.0)). So we don't
     // care about LDR precision since they all resolve to LDR-max. For appropriately mastered HDR
@@ -168,7 +168,7 @@
     LOG_ALWAYS_FATAL_IF(!blurSurface, "%s: Failed to create surface!", __func__);
     return makeImage(blurSurface.get(), blurBuilder);
 }
-sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio,
+sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, float inputMultiplier,
                                   float targetHdrSdrRatio) const {
     static constexpr float kScaleFactor = 1.0f / 128.0f;
     SkRuntimeShaderBuilder tonemapBuilder(mTonemap);
@@ -177,7 +177,7 @@
             localLux->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                     SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));
     tonemapBuilder.uniform("scaleFactor") = kScaleFactor;
-    tonemapBuilder.uniform("hdrSdrRatio") = hdrSdrRatio;
+    tonemapBuilder.uniform("inputMultiplier") = inputMultiplier;
     tonemapBuilder.uniform("targetHdrSdrRatio") = targetHdrSdrRatio;
     return tonemapBuilder.makeShader();
 }
diff --git a/libs/renderengine/skia/filters/MouriMap.h b/libs/renderengine/skia/filters/MouriMap.h
index 9ba2b6f..f4bfa15 100644
--- a/libs/renderengine/skia/filters/MouriMap.h
+++ b/libs/renderengine/skia/filters/MouriMap.h
@@ -62,10 +62,13 @@
 public:
     MouriMap();
     // Apply the MouriMap tonemmaping operator to the input.
-    // The HDR/SDR ratio describes the luminace range of the input. 1.0 means SDR. Anything larger
-    // then 1.0 means that there is headroom above the SDR region.
-    // Similarly, the target HDR/SDR ratio describes the luminance range of the output.
-    sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float inputHdrSdrRatio,
+    // The inputMultiplier informs how to interpret the luminance encoding of the input.
+    // For a fixed point input, this is necessary to interpret what "1.0" means for the input
+    // pixels, so that the luminance can be scaled appropriately, such that the operator can
+    // transform SDR values to be within 1.0. For a floating point input, "1.0" always means SDR,
+    // so the caller must pass 1.0.
+    // The target HDR/SDR ratio describes the luminance range of the output.
+    sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float inputMultiplier,
                              float targetHdrSdrRatio);
 
 private:
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index c187f93..ea5605d 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -249,11 +249,10 @@
     return;
 }
 
-void RenderEngineThreaded::drawGainmapInternal(
+void RenderEngineThreaded::tonemapAndDrawGainmapInternal(
         const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
-        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
         const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
-        float hdrSdrRatio, ui::Dataspace dataspace,
+        float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
         const std::shared_ptr<ExternalTexture>& gainmap) {
     resultPromise->set_value(Fence::NO_FENCE);
     return;
@@ -281,10 +280,9 @@
     return resultFuture;
 }
 
-ftl::Future<FenceResult> RenderEngineThreaded::drawGainmap(
-        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+ftl::Future<FenceResult> RenderEngineThreaded::tonemapAndDrawGainmap(
         const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
-        float hdrSdrRatio, ui::Dataspace dataspace,
+        float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
         const std::shared_ptr<ExternalTexture>& gainmap) {
     SFTRACE_CALL();
     const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
@@ -292,13 +290,14 @@
     {
         std::lock_guard lock(mThreadMutex);
         mNeedsPostRenderCleanup = true;
-        mFunctionCalls.push([resultPromise, sdr, sdrFence = std::move(sdrFence), hdr,
-                             hdrFence = std::move(hdrFence), hdrSdrRatio, dataspace,
+        mFunctionCalls.push([resultPromise, hdr, hdrFence = std::move(hdrFence), hdrSdrRatio,
+                             dataspace, sdr,
                              gainmap](renderengine::RenderEngine& instance) mutable {
-            SFTRACE_NAME("REThreaded::drawGainmap");
-            instance.updateProtectedContext({}, {sdr.get(), hdr.get(), gainmap.get()});
-            instance.drawGainmapInternal(std::move(resultPromise), sdr, std::move(sdrFence), hdr,
-                                         std::move(hdrFence), hdrSdrRatio, dataspace, gainmap);
+            SFTRACE_NAME("REThreaded::tonemapAndDrawGainmap");
+            instance.updateProtectedContext({}, {hdr.get(), sdr.get(), gainmap.get()});
+            instance.tonemapAndDrawGainmapInternal(std::move(resultPromise), hdr,
+                                                   std::move(hdrFence), hdrSdrRatio, dataspace, sdr,
+                                                   gainmap);
         });
     }
     mCondition.notify_one();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index cb6e924..8554b55 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -55,12 +55,10 @@
                                         const std::vector<LayerSettings>& layers,
                                         const std::shared_ptr<ExternalTexture>& buffer,
                                         base::unique_fd&& bufferFence) override;
-    ftl::Future<FenceResult> drawGainmap(const std::shared_ptr<ExternalTexture>& sdr,
-                                         base::borrowed_fd&& sdrFence,
-                                         const std::shared_ptr<ExternalTexture>& hdr,
-                                         base::borrowed_fd&& hdrFence, float hdrSdrRatio,
-                                         ui::Dataspace dataspace,
-                                         const std::shared_ptr<ExternalTexture>& gainmap) override;
+    ftl::Future<FenceResult> tonemapAndDrawGainmap(
+            const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+            float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
+            const std::shared_ptr<ExternalTexture>& gainmap) override;
 
     int getContextPriority() override;
     bool supportsBackgroundBlur() override;
@@ -77,13 +75,11 @@
                             const std::vector<LayerSettings>& layers,
                             const std::shared_ptr<ExternalTexture>& buffer,
                             base::unique_fd&& bufferFence) override;
-    void drawGainmapInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
-                             const std::shared_ptr<ExternalTexture>& sdr,
-                             base::borrowed_fd&& sdrFence,
-                             const std::shared_ptr<ExternalTexture>& hdr,
-                             base::borrowed_fd&& hdrFence, float hdrSdrRatio,
-                             ui::Dataspace dataspace,
-                             const std::shared_ptr<ExternalTexture>& gainmap) override;
+    void tonemapAndDrawGainmapInternal(
+            const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+            const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+            float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
+            const std::shared_ptr<ExternalTexture>& gainmap) override;
 
 private:
     void threadMain(CreateInstanceFactory factory);
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index 8d6f74b..c9f0761 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -417,6 +417,7 @@
     return DisplayIdentificationInfo{
             .id = displayId,
             .name = std::string(edid->displayName),
+            .port = port,
             .deviceProductInfo = buildDeviceProductInfo(*edid),
             .preferredDetailedTimingDescriptor = edid->preferredDetailedTimingDescriptor,
     };
diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index b0c6e44..18c9a6b 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -596,6 +596,8 @@
             width = height = stride = format = usage_deprecated = 0;
             layerCount = 0;
             usage = 0;
+            native_handle_close(handle);
+            native_handle_delete(const_cast<native_handle_t*>(handle));
             handle = nullptr;
             ALOGE("unflatten: registerBuffer failed: %s (%d)", strerror(-err), err);
             return err;
diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
index cf67d7b..cdac269 100644
--- a/libs/ui/include/ui/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -42,6 +42,7 @@
 struct DisplayIdentificationInfo {
     PhysicalDisplayId id;
     std::string name;
+    uint8_t port;
     std::optional<DeviceProductInfo> deviceProductInfo;
     std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor;
 };
diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h
index e87931e..7c17763 100644
--- a/libs/ui/include/ui/PublicFormat.h
+++ b/libs/ui/include/ui/PublicFormat.h
@@ -60,6 +60,7 @@
     JPEG_R = 0x1005,
     HEIC = 0x48454946,
     HEIC_ULTRAHDR = 0x1006,
+    YCBCR_P210 = 0x3c,
 };
 
 /* Convert from android.graphics.ImageFormat/PixelFormat enums to graphics.h HAL
diff --git a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
index 5c12323..fc0aef4 100644
--- a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
+++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
@@ -39,29 +39,9 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class GpuWorkTracepointTest extends BaseHostJUnit4Test {
 
-    private static final String CPU_FREQUENCY_TRACEPOINT_FORMAT_PATH =
-            "/sys/kernel/tracing/events/power/cpu_frequency/format";
     private static final String GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH =
             "/sys/kernel/tracing/events/power/gpu_work_period/format";
 
-    @Test
-    public void testReadTracingEvents() throws Exception {
-        // Test |testGpuWorkPeriodTracepointFormat| is dependent on whether certain tracepoint
-        // paths exist. This means the test will vacuously pass if the tracepoint file system is
-        // inaccessible. Thus, as a basic check, we make sure the CPU frequency tracepoint format
-        // is accessible. If not, something is probably fundamentally broken about the tracing
-        // file system.
-        CommandResult commandResult = getDevice().executeShellV2Command(
-                String.format("cat %s", CPU_FREQUENCY_TRACEPOINT_FORMAT_PATH));
-
-        assertEquals(String.format(
-                        "Failed to read \"%s\". This probably means that the tracing file system "
-                                + "is fundamentally broken in some way, possibly due to bad "
-                                + "permissions.",
-                        CPU_FREQUENCY_TRACEPOINT_FORMAT_PATH),
-                commandResult.getStatus(), CommandStatus.SUCCESS);
-    }
-
     @VsrTest(requirements={"VSR-3.3-004"})
     @RequiresDevice
     @Test
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 0f8d38f..71c779e 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -42,12 +42,12 @@
     name: "libsurfaceflinger_defaults",
     defaults: [
         "android.hardware.graphics.composer3-ndk_shared",
-        "android.hardware.power-ndk_shared",
         "librenderengine_deps",
         "libtimestats_deps",
         "libsurfaceflinger_common_deps",
         "surfaceflinger_defaults",
         "libsurfaceflinger_proto_deps",
+        "poweradvisor_deps",
     ],
     cflags: [
         "-DLOG_TAG=\"SurfaceFlinger\"",
@@ -79,7 +79,6 @@
         "libhidlbase",
         "liblog",
         "libnativewindow",
-        "libpowermanager",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
         "libstatslog_surfaceflinger",
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 82eafd4..2d0f874 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -13,11 +13,11 @@
     defaults: [
         "aconfig_lib_cc_static_link.defaults",
         "android.hardware.graphics.composer3-ndk_shared",
-        "android.hardware.power-ndk_shared",
         "librenderengine_deps",
         "libtimestats_deps",
         "surfaceflinger_defaults",
         "libsurfaceflinger_proto_deps",
+        "poweradvisor_deps",
     ],
     cflags: [
         "-DLOG_TAG=\"CompositionEngine\"",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index 2e7a7d9..c1b864d 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -118,7 +118,8 @@
     // isPeekingThrough specifies whether this layer will be shown through a
     // hole punch in a layer above it.
     virtual void writeStateToHWC(bool includeGeometry, bool skipLayer, uint32_t z,
-                                 bool zIsOverridden, bool isPeekingThrough) = 0;
+                                 bool zIsOverridden, bool isPeekingThrough,
+                                 bool isLutSupported) = 0;
 
     // Updates the cursor position with the HWC
     virtual void writeCursorPositionToHWC() const = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 712b551..0063eee 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -58,7 +58,7 @@
                                 const std::optional<std::vector<std::optional<LutProperties>>>
                                         properties = std::nullopt) override;
     void writeStateToHWC(bool includeGeometry, bool skipLayer, uint32_t z, bool zIsOverridden,
-                         bool isPeekingThrough) override;
+                         bool isPeekingThrough, bool hasLutsProperties) override;
     void writeCursorPositionToHWC() const override;
 
     HWC2::Layer* getHwcLayer() const override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index 9333ebb..09c47f0 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -47,7 +47,7 @@
                 (bool, bool, ui::Transform::RotationFlags,
                  (const std::optional<std::vector<std::optional<
                           aidl::android::hardware::graphics::composer3::LutProperties>>>)));
-    MOCK_METHOD5(writeStateToHWC, void(bool, bool, uint32_t, bool, bool));
+    MOCK_METHOD(void, writeStateToHWC, (bool, bool, uint32_t, bool, bool, bool));
     MOCK_CONST_METHOD0(writeCursorPositionToHWC, void());
 
     MOCK_CONST_METHOD0(getHwcLayer, HWC2::Layer*());
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index f9ed92d..4c55615 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -909,6 +909,9 @@
 
     applyPictureProfile();
 
+    auto* properties = getOverlaySupport();
+    bool hasLutsProperties = properties && properties->lutProperties.has_value();
+
     compositionengine::OutputLayer* peekThroughLayer = nullptr;
     sp<GraphicBuffer> previousOverride = nullptr;
     bool includeGeometry = refreshArgs.updatingGeometryThisFrame;
@@ -940,7 +943,7 @@
                     includeGeometry = true;
                     constexpr bool isPeekingThrough = true;
                     peekThroughLayer->writeStateToHWC(includeGeometry, false, z++, overrideZ,
-                                                      isPeekingThrough);
+                                                      isPeekingThrough, hasLutsProperties);
                     outputLayerHash ^= android::hashCombine(
                             reinterpret_cast<uint64_t>(&peekThroughLayer->getLayerFE()),
                             z, includeGeometry, overrideZ, isPeekingThrough,
@@ -952,7 +955,8 @@
         }
 
         constexpr bool isPeekingThrough = false;
-        layer->writeStateToHWC(includeGeometry, skipLayer, z++, overrideZ, isPeekingThrough);
+        layer->writeStateToHWC(includeGeometry, skipLayer, z++, overrideZ, isPeekingThrough,
+                               hasLutsProperties);
         if (!skipLayer) {
             outputLayerHash ^= android::hashCombine(
                     reinterpret_cast<uint64_t>(&layer->getLayerFE()),
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index a040c88..96b86d5 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -449,7 +449,8 @@
 }
 
 void OutputLayer::writeStateToHWC(bool includeGeometry, bool skipLayer, uint32_t z,
-                                  bool zIsOverridden, bool isPeekingThrough) {
+                                  bool zIsOverridden, bool isPeekingThrough,
+                                  bool hasLutsProperties) {
     const auto& state = getState();
     // Skip doing this if there is no HWC interface
     if (!state.hwc) {
@@ -491,8 +492,9 @@
 
     writeCompositionTypeToHWC(hwcLayer.get(), requestedCompositionType, isPeekingThrough,
                               skipLayer);
-
-    writeLutToHWC(hwcLayer.get(), *outputIndependentState);
+    if (hasLutsProperties) {
+        writeLutToHWC(hwcLayer.get(), *outputIndependentState);
+    }
 
     if (requestedCompositionType == Composition::SOLID_COLOR) {
         writeSolidColorStateToHWC(hwcLayer.get(), *outputIndependentState);
@@ -589,28 +591,29 @@
 
 void OutputLayer::writeLutToHWC(HWC2::Layer* hwcLayer,
                                 const LayerFECompositionState& outputIndependentState) {
-    if (!outputIndependentState.luts) {
-        return;
-    }
-    auto& lutFileDescriptor = outputIndependentState.luts->getLutFileDescriptor();
-    auto lutOffsets = outputIndependentState.luts->offsets;
-    auto& lutProperties = outputIndependentState.luts->lutProperties;
-
-    std::vector<LutProperties> aidlProperties;
-    aidlProperties.reserve(lutProperties.size());
-    for (size_t i = 0; i < lutOffsets.size(); i++) {
-        LutProperties properties;
-        properties.dimension = static_cast<LutProperties::Dimension>(lutProperties[i].dimension);
-        properties.size = lutProperties[i].size;
-        properties.samplingKeys = {
-                static_cast<LutProperties::SamplingKey>(lutProperties[i].samplingKey)};
-        aidlProperties.emplace_back(properties);
-    }
-
     Luts luts;
-    luts.pfd = ndk::ScopedFileDescriptor(dup(lutFileDescriptor.get()));
-    luts.offsets = lutOffsets;
-    luts.lutProperties = std::move(aidlProperties);
+    // if outputIndependentState.luts is nullptr, it means we want to clear the LUTs
+    // and we pass an empty Luts object to the HWC.
+    if (outputIndependentState.luts) {
+        auto& lutFileDescriptor = outputIndependentState.luts->getLutFileDescriptor();
+        auto lutOffsets = outputIndependentState.luts->offsets;
+        auto& lutProperties = outputIndependentState.luts->lutProperties;
+
+        std::vector<LutProperties> aidlProperties;
+        aidlProperties.reserve(lutProperties.size());
+        for (size_t i = 0; i < lutOffsets.size(); i++) {
+            aidlProperties.emplace_back(
+                    LutProperties{.dimension = static_cast<LutProperties::Dimension>(
+                                          lutProperties[i].dimension),
+                                  .size = lutProperties[i].size,
+                                  .samplingKeys = {static_cast<LutProperties::SamplingKey>(
+                                          lutProperties[i].samplingKey)}});
+        }
+
+        luts.pfd = ndk::ScopedFileDescriptor(dup(lutFileDescriptor.get()));
+        luts.offsets = lutOffsets;
+        luts.lutProperties = std::move(aidlProperties);
+    }
 
     switch (auto error = hwcLayer->setLuts(luts)) {
         case hal::Error::NONE:
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index dbffe80..ca262ee 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -541,6 +541,9 @@
     MOCK_CONST_METHOD1(calculateOutputSourceCrop, FloatRect(uint32_t));
     MOCK_CONST_METHOD0(calculateOutputDisplayFrame, Rect());
     MOCK_CONST_METHOD1(calculateOutputRelativeBufferTransform, uint32_t(uint32_t));
+    MOCK_METHOD(void, updateLuts,
+                (const LayerFECompositionState&,
+                 const std::optional<std::vector<std::optional<LutProperties>>>&));
 
     // compositionengine::OutputLayer overrides
     const compositionengine::Output& getOutput() const override { return mOutput; }
@@ -985,21 +988,24 @@
     EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCState) {
     mOutputLayer.editState().hwc.reset();
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCLayer) {
     mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc(nullptr);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetAllState) {
@@ -1010,7 +1016,8 @@
     EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerTest, displayInstallOrientationBufferTransformSetTo90) {
@@ -1041,7 +1048,8 @@
     expectSetColorCall();
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSideband) {
@@ -1052,7 +1060,8 @@
     expectSetCompositionTypeCall(Composition::SIDEBAND);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForCursor) {
@@ -1063,7 +1072,8 @@
     expectSetCompositionTypeCall(Composition::CURSOR);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForDevice) {
@@ -1074,7 +1084,8 @@
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsNotSetIfUnchanged) {
@@ -1087,7 +1098,8 @@
     expectNoSetCompositionTypeCall();
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfColorTransformNotSupported) {
@@ -1098,7 +1110,8 @@
     expectSetCompositionTypeCall(Composition::CLIENT);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfClientCompositionForced) {
@@ -1111,7 +1124,8 @@
     expectSetCompositionTypeCall(Composition::CLIENT);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, allStateIncludesMetadataIfPresent) {
@@ -1125,7 +1139,8 @@
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, perFrameStateDoesNotIncludeMetadataIfPresent) {
@@ -1137,7 +1152,8 @@
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, overriddenSkipLayerDoesNotSendBuffer) {
@@ -1152,7 +1168,8 @@
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, overriddenSkipLayerForSolidColorDoesNotSendBuffer) {
@@ -1167,7 +1184,8 @@
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoIfPresent) {
@@ -1182,7 +1200,8 @@
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoForSolidColorIfPresent) {
@@ -1197,7 +1216,8 @@
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, previousOverriddenLayerSendsSurfaceDamage) {
@@ -1211,7 +1231,8 @@
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, previousSkipLayerSendsUpdatedDeviceCompositionInfo) {
@@ -1227,7 +1248,8 @@
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, previousSkipLayerSendsUpdatedClientCompositionInfo) {
@@ -1244,7 +1266,8 @@
     expectSetCompositionTypeCall(Composition::CLIENT);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, peekThroughChangesBlendMode) {
@@ -1258,7 +1281,8 @@
     expectPerFrameCommonCalls();
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, isPeekingThroughSetsOverride) {
@@ -1266,7 +1290,8 @@
     expectPerFrameCommonCalls();
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ true);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ true,
+                                 /*hasLutsProperties*/ false);
     EXPECT_TRUE(mOutputLayer.getState().hwc->stateOverridden);
 }
 
@@ -1276,7 +1301,7 @@
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ true, /*isPeekingThrough*/
-                                 false);
+                                 false, /*hasLutsProperties*/ false);
     EXPECT_TRUE(mOutputLayer.getState().hwc->stateOverridden);
 }
 
@@ -1288,7 +1313,7 @@
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/
-                                 false);
+                                 false, /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, roundedCornersPeekingThroughAllowsDeviceComposition) {
@@ -1301,7 +1326,7 @@
     mLayerFEState.compositionType = Composition::DEVICE;
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/
-                                 true);
+                                 true, /*hasLutsProperties*/ false);
     EXPECT_EQ(Composition::DEVICE, mOutputLayer.getState().hwc->hwcCompositionType);
 }
 
@@ -1318,7 +1343,7 @@
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/
-                                 false);
+                                 false, /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, setCompositionTypeRefreshRateIndicator) {
@@ -1330,7 +1355,8 @@
     expectSetCompositionTypeCall(Composition::REFRESH_RATE_INDICATOR);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, setsPictureProfileWhenCommitted) {
@@ -1349,7 +1375,8 @@
 
     mOutputLayer.commitPictureProfileToCompositionState();
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, doesNotSetPictureProfileWhenNotCommitted) {
@@ -1367,7 +1394,8 @@
     EXPECT_CALL(*mHwcLayer, setPictureProfileHandle(_)).Times(0);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, doesNotSetPictureProfileWhenNotCommittedLater) {
@@ -1386,7 +1414,8 @@
 
     mOutputLayer.commitPictureProfileToCompositionState();
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 
     expectGeometryCommonCalls();
     expectPerFrameCommonCalls();
@@ -1395,7 +1424,8 @@
     EXPECT_CALL(*mHwcLayer, setPictureProfileHandle(PictureProfileHandle(1))).Times(0);
     // No committing of picture profile before writing the state
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
 }
 
 /*
@@ -1441,21 +1471,24 @@
     mLayerFEState.buffer = kBuffer1;
     EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 0, kBuffer1, kFence));
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
     Mock::VerifyAndClearExpectations(&mHwcLayer);
 
     // Buffer2 is stored in slot 1
     mLayerFEState.buffer = kBuffer2;
     EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer2, kFence));
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
     Mock::VerifyAndClearExpectations(&mHwcLayer);
 
     // Buffer3 is stored in slot 2
     mLayerFEState.buffer = kBuffer3;
     EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 2, kBuffer3, kFence));
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
     Mock::VerifyAndClearExpectations(&mHwcLayer);
 
     // Buffer2 becomes the active buffer again (with a nullptr) and reuses slot 1
@@ -1463,7 +1496,8 @@
     sp<GraphicBuffer> nullBuffer = nullptr;
     EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, nullBuffer, kFence));
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
     Mock::VerifyAndClearExpectations(&mHwcLayer);
 
     // Buffer slots are cleared
@@ -1481,7 +1515,8 @@
     mLayerFEState.buffer = kBuffer1;
     EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer1, kFence));
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
-                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                 /*hasLutsProperties*/ false);
     Mock::VerifyAndClearExpectations(&mHwcLayer);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 442b603..109d3ed 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -813,19 +813,22 @@
                 updateCompositionState(false, false, ui::Transform::ROT_180, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer2.outputLayer,
                 updateCompositionState(false, false, ui::Transform::ROT_180, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer3.outputLayer,
                 updateCompositionState(false, false, ui::Transform::ROT_180, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
 
     injectOutputLayer(layer1);
@@ -852,17 +855,20 @@
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
 
     injectOutputLayer(layer1);
@@ -888,17 +894,20 @@
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
 
     injectOutputLayer(layer1);
@@ -932,7 +941,8 @@
     uint32_t z = 0;
     EXPECT_CALL(*layer0.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer0.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
 
     // After calling planComposition (which clears overrideInfo), this test sets
@@ -942,15 +952,17 @@
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ true, /*isPeekingThrough*/
-                                true));
+                                true, /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ true, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ true, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, z++,
-                                /*zIsOverridden*/ true, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ true, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
 
     injectOutputLayer(layer0);
@@ -4962,12 +4974,14 @@
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
 
     layer2.layerFEState.backgroundBlurRadius = 10;
@@ -4996,17 +5010,20 @@
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
 
     layer2.layerFEState.backgroundBlurRadius = 10;
@@ -5036,17 +5053,20 @@
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
-                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
+                                /*zIsOverridden*/ false, /*isPeekingThrough*/ false,
+                                /*hasLutsProperties*/ false));
     EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
 
     BlurRegion region;
@@ -5101,13 +5121,13 @@
     // Because StrictMock
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(_, _, _, _));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(_, _, _, _, _));
+    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(_, _, _, _, _, _));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(_, _, _, _));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(_, _, _, _, _));
+    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(_, _, _, _, _, _));
     EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(_, _, _, _));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(_, _, _, _, _));
+    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(_, _, _, _, _, _));
 
     // No layer picture profiles should be committed
     EXPECT_CALL(*layer1.outputLayer, commitPictureProfileToCompositionState).Times(0);
@@ -5164,13 +5184,13 @@
     // Because StrictMock
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(_, _, _, _));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(_, _, _, _, _));
+    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(_, _, _, _, _, _));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(_, _, _, _));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(_, _, _, _, _));
+    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(_, _, _, _, _, _));
     EXPECT_CALL(*layer3.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(_, _, _, _));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(_, _, _, _, _));
+    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(_, _, _, _, _, _));
 
     // The two highest priority layers should have their picture profiles committed
     EXPECT_CALL(*layer1.outputLayer, commitPictureProfileToCompositionState).Times(0);
diff --git a/services/surfaceflinger/Display/DisplaySnapshot.cpp b/services/surfaceflinger/Display/DisplaySnapshot.cpp
index 0c7a58e..3960740 100644
--- a/services/surfaceflinger/Display/DisplaySnapshot.cpp
+++ b/services/surfaceflinger/Display/DisplaySnapshot.cpp
@@ -26,11 +26,12 @@
 
 namespace android::display {
 
-DisplaySnapshot::DisplaySnapshot(PhysicalDisplayId displayId,
+DisplaySnapshot::DisplaySnapshot(PhysicalDisplayId displayId, uint8_t port,
                                  ui::DisplayConnectionType connectionType,
                                  DisplayModes&& displayModes, ui::ColorModes&& colorModes,
                                  std::optional<DeviceProductInfo>&& deviceProductInfo)
       : mDisplayId(displayId),
+        mPort(port),
         mConnectionType(connectionType),
         mDisplayModes(std::move(displayModes)),
         mColorModes(std::move(colorModes)),
@@ -62,6 +63,8 @@
 void DisplaySnapshot::dump(utils::Dumper& dumper) const {
     using namespace std::string_view_literals;
 
+    dumper.dump("port"sv, mPort);
+
     dumper.dump("connectionType"sv, ftl::enum_string(mConnectionType));
 
     dumper.dump("colorModes"sv);
diff --git a/services/surfaceflinger/Display/DisplaySnapshot.h b/services/surfaceflinger/Display/DisplaySnapshot.h
index 23471f5..0030aad 100644
--- a/services/surfaceflinger/Display/DisplaySnapshot.h
+++ b/services/surfaceflinger/Display/DisplaySnapshot.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <cstdint>
 #include <optional>
 
 #include <ui/ColorMode.h>
@@ -30,13 +31,14 @@
 // Immutable state of a physical display, captured on hotplug.
 class DisplaySnapshot {
 public:
-    DisplaySnapshot(PhysicalDisplayId, ui::DisplayConnectionType, DisplayModes&&, ui::ColorModes&&,
-                    std::optional<DeviceProductInfo>&&);
+    DisplaySnapshot(PhysicalDisplayId, uint8_t, ui::DisplayConnectionType, DisplayModes&&,
+                    ui::ColorModes&&, std::optional<DeviceProductInfo>&&);
 
     DisplaySnapshot(const DisplaySnapshot&) = delete;
     DisplaySnapshot(DisplaySnapshot&&) = default;
 
     PhysicalDisplayId displayId() const { return mDisplayId; }
+    uint8_t port() const { return mPort; }
     ui::DisplayConnectionType connectionType() const { return mConnectionType; }
 
     std::optional<DisplayModeId> translateModeId(hal::HWConfigId) const;
@@ -51,6 +53,7 @@
 
 private:
     const PhysicalDisplayId mDisplayId;
+    const uint8_t mPort;
     const ui::DisplayConnectionType mConnectionType;
 
     // Effectively const except in move constructor.
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 55ccdef..721cfd3 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -1180,6 +1180,7 @@
             return DisplayIdentificationInfo{.id = PhysicalDisplayId::fromPort(port),
                                              .name = isPrimary ? "Primary display"
                                                                : "Secondary display",
+                                             .port = port,
                                              .deviceProductInfo = std::nullopt};
         }();
 
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 4d9a9ca..022588d 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -262,7 +262,10 @@
     snapshot.isVisible = visible;
 
     if (FlagManager::getInstance().skip_invisible_windows_in_input()) {
-        snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+        const bool visibleForInput =
+                snapshot.isVisible || (snapshot.hasInputInfo() && !snapshot.isHiddenByPolicy());
+        snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE,
+                                          !visibleForInput);
     } else {
         // TODO(b/238781169) we are ignoring this compat for now, since we will have
         // to remove any optimization based on visibility.
diff --git a/services/surfaceflinger/PowerAdvisor/Common.h b/services/surfaceflinger/PowerAdvisor/Common.h
new file mode 100644
index 0000000..b4a87dd
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/Common.h
@@ -0,0 +1,28 @@
+/*
+ * 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
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <aidl/android/adpf/ISessionManager.h>
+#include <aidl/android/hardware/power/CompositionData.h>
+#pragma clang diagnostic pop
+
+namespace android::adpf {
+using namespace ::aidl::android::adpf;
+namespace hal = ::aidl::android::hardware::power;
+} // namespace android::adpf
diff --git a/services/surfaceflinger/PowerAdvisor/PowerAdvisor.cpp b/services/surfaceflinger/PowerAdvisor/PowerAdvisor.cpp
index c7d0b2c..ff45272 100644
--- a/services/surfaceflinger/PowerAdvisor/PowerAdvisor.cpp
+++ b/services/surfaceflinger/PowerAdvisor/PowerAdvisor.cpp
@@ -28,22 +28,19 @@
 #include <optional>
 
 #include <android-base/properties.h>
+#include <android/binder_libbinder.h>
 #include <common/trace.h>
 #include <utils/Log.h>
 #include <utils/Mutex.h>
 
 #include <binder/IServiceManager.h>
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
 #include <powermanager/PowerHalController.h>
 #include <powermanager/PowerHintSessionWrapper.h>
-#pragma clang diagnostic pop
 
 #include <common/FlagManager.h>
 #include "PowerAdvisor.h"
-
-namespace hal = aidl::android::hardware::power;
+#include "SessionManager.h"
 
 namespace android::adpf::impl {
 
@@ -545,6 +542,18 @@
     mTotalFrameTargetDuration = targetDuration;
 }
 
+std::shared_ptr<SessionManager> PowerAdvisor::getSessionManager() {
+    return mSessionManager;
+}
+
+sp<IBinder> PowerAdvisor::getOrCreateSessionManagerForBinder(uid_t uid) {
+    // Flag guards the creation of SessionManager
+    if (mSessionManager == nullptr && FlagManager::getInstance().adpf_native_session_manager()) {
+        mSessionManager = ndk::SharedRefBase::make<SessionManager>(uid);
+    }
+    return AIBinder_toPlatformBinder(mSessionManager->asBinder().get());
+}
+
 std::vector<DisplayId> PowerAdvisor::getOrderedDisplayIds(
         std::optional<TimePoint> DisplayTimingData::*sortBy) {
     std::vector<DisplayId> sortedDisplays;
diff --git a/services/surfaceflinger/PowerAdvisor/PowerAdvisor.h b/services/surfaceflinger/PowerAdvisor/PowerAdvisor.h
index 458b46d..43fc210 100644
--- a/services/surfaceflinger/PowerAdvisor/PowerAdvisor.h
+++ b/services/surfaceflinger/PowerAdvisor/PowerAdvisor.h
@@ -36,6 +36,8 @@
 #include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
 
+#include "SessionManager.h"
+
 using namespace std::chrono_literals;
 
 namespace android {
@@ -47,6 +49,8 @@
 
 namespace adpf {
 
+namespace hal = aidl::android::hardware::power;
+
 class PowerAdvisor {
 public:
     virtual ~PowerAdvisor() = default;
@@ -102,12 +106,18 @@
     virtual void setDisplays(std::vector<DisplayId>& displayIds) = 0;
     // Sets the target duration for the entire pipeline including the gpu
     virtual void setTotalFrameTargetWorkDuration(Duration targetDuration) = 0;
+    // Get the session manager, if it exists
+    virtual std::shared_ptr<SessionManager> getSessionManager() = 0;
 
     // --- The following methods may run on threads besides SF main ---
     // Send a hint about an upcoming increase in the CPU workload
     virtual void notifyCpuLoadUp() = 0;
     // Send a hint about the imminent start of a new CPU workload
     virtual void notifyDisplayUpdateImminentAndCpuReset() = 0;
+
+    // --- The following methods specifically run on binder threads ---
+    // Retrieve  a SessionManager for HintManagerService to call
+    virtual sp<IBinder> getOrCreateSessionManagerForBinder(uid_t uid) = 0;
 };
 
 namespace impl {
@@ -146,11 +156,15 @@
     void setCompositeEnd(TimePoint compositeEndTime) override;
     void setDisplays(std::vector<DisplayId>& displayIds) override;
     void setTotalFrameTargetWorkDuration(Duration targetDuration) override;
+    std::shared_ptr<SessionManager> getSessionManager() override;
 
     // --- The following methods may run on threads besides SF main ---
     void notifyCpuLoadUp() override;
     void notifyDisplayUpdateImminentAndCpuReset() override;
 
+    // --- The following methods specifically run on binder threads ---
+    sp<IBinder> getOrCreateSessionManagerForBinder(uid_t uid) override;
+
 private:
     friend class PowerAdvisorTest;
 
@@ -323,6 +337,8 @@
     template <aidl::android::hardware::power::ChannelMessage::ChannelMessageContents::Tag T,
               class In>
     bool writeHintSessionMessage(In* elements, size_t count) REQUIRES(mHintSessionMutex);
+
+    std::shared_ptr<SessionManager> mSessionManager;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/PowerAdvisor/SessionLayerMap.cpp b/services/surfaceflinger/PowerAdvisor/SessionLayerMap.cpp
new file mode 100644
index 0000000..9f95163
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/SessionLayerMap.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SessionLayerMap.h"
+#include <android/binder_libbinder.h>
+
+namespace android::adpf {
+
+void SessionLayerMap::notifySessionsDied(std::vector<int32_t>& sessionIds) {
+    for (int id : sessionIds) {
+        auto&& iter = mSessions.find(id);
+        if (iter != mSessions.end()) {
+            mSessions.erase(iter);
+        }
+    }
+}
+
+void SessionLayerMap::notifyLayersDied(std::vector<int32_t>& layers) {
+    for (auto&& layer : layers) {
+        auto&& iter = mLayers.find(layer);
+        if (iter != mLayers.end()) {
+            mLayers.erase(iter);
+        }
+    }
+}
+
+bool SessionLayerMap::bindSessionIDToLayers(int sessionId, const std::vector<int32_t>& layerIds) {
+    // If there is no association, just drop from map
+    if (layerIds.empty()) {
+        mSessions.erase(sessionId);
+        return false;
+    }
+
+    // Ensure session exists
+    if (!mSessions.contains(sessionId)) {
+        mSessions.emplace(sessionId, MappedType(sessionId, mLayers));
+    }
+
+    MappedType& session = mSessions.at(sessionId);
+    std::set<int32_t> newLinks;
+
+    // For each incoming link
+    for (auto&& layerId : layerIds) {
+        auto&& iter = mLayers.find(layerId);
+
+        // If it's not in the map, add it
+        if (iter == mLayers.end()) {
+            mLayers.emplace(layerId, MappedType(layerId, mSessions));
+        }
+
+        // Make a ref to it in the session's new association map
+        newLinks.insert(layerId);
+    }
+
+    session.swapLinks(std::move(newLinks));
+    return true;
+}
+
+void SessionLayerMap::getAssociatedSessions(int32_t layerId, std::vector<int32_t>& sessionIdsOut) {
+    sessionIdsOut.clear();
+    auto&& iter = mLayers.find(layerId);
+
+    if (iter == mLayers.end()) {
+        return;
+    }
+
+    // Dump the internal association set into this vector
+    sessionIdsOut.insert(sessionIdsOut.begin(), iter->second.mLinks.begin(),
+                         iter->second.mLinks.end());
+}
+
+void SessionLayerMap::getCurrentlyRelevantLayers(
+        std::unordered_set<int32_t>& currentlyRelevantLayers) {
+    currentlyRelevantLayers.clear();
+    for (auto&& layer : mLayers) {
+        currentlyRelevantLayers.insert(layer.first);
+    }
+}
+
+} // namespace android::adpf
\ No newline at end of file
diff --git a/services/surfaceflinger/PowerAdvisor/SessionLayerMap.h b/services/surfaceflinger/PowerAdvisor/SessionLayerMap.h
new file mode 100644
index 0000000..51808a6
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/SessionLayerMap.h
@@ -0,0 +1,111 @@
+/*
+ * 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 <log/log.h>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android::adpf {
+
+class SessionLayerMap {
+public:
+    // Inform the SessionLayerMap about dead sessions
+    void notifySessionsDied(std::vector<int32_t>& sessionIds);
+    // Inform the SessionLayerMap about dead layers
+    void notifyLayersDied(std::vector<int32_t>& layers);
+    // Associate a session with a specific set of layer ids
+    bool bindSessionIDToLayers(int sessionId, const std::vector<int32_t>& layerIds);
+    // Get the set of sessions that are mapped to a specific layer id
+    void getAssociatedSessions(int32_t layerId, std::vector<int32_t>& sessionIdsOut);
+    // Get the set of layers that are currently being tracked
+    void getCurrentlyRelevantLayers(std::unordered_set<int32_t>& currentlyRelevantLayers);
+
+private:
+    struct MappedType {
+        MappedType(int32_t id, std::unordered_map<int32_t, MappedType>& otherList)
+              : mId(id), mOtherList(otherList) {};
+        MappedType() = delete;
+        ~MappedType() { swapLinks({}); }
+
+        // Replace the set of associated IDs for this mapped type with a different set of IDs,
+        // updating only associations which have changed between the two sets
+        void swapLinks(std::set<int32_t>&& incoming) {
+            auto&& oldIter = mLinks.begin();
+            auto&& newIter = incoming.begin();
+
+            // Dump all outdated values and insert new ones
+            while (oldIter != mLinks.end() || newIter != incoming.end()) {
+                // If there is a value in the new set but not the old set
+                // We should have already ensured what we're linking to exists
+                if (oldIter == mLinks.end() || (newIter != incoming.end() && *newIter < *oldIter)) {
+                    addRemoteAssociation(*newIter);
+                    ++newIter;
+                    continue;
+                }
+
+                // If there is a value in the old set but not the new set
+                if (newIter == incoming.end() || (oldIter != mLinks.end() && *oldIter < *newIter)) {
+                    dropRemoteAssociation(*oldIter);
+                    ++oldIter;
+                    continue;
+                }
+
+                // If they're the same, skip
+                if (*oldIter == *newIter) {
+                    ++oldIter;
+                    ++newIter;
+                    continue;
+                }
+            }
+
+            mLinks.swap(incoming);
+        }
+
+        void addRemoteAssociation(int32_t other) {
+            auto&& iter = mOtherList.find(other);
+            if (iter != mOtherList.end()) {
+                iter->second.mLinks.insert(mId);
+            } else {
+                ALOGE("Existing entry in SessionLayerMap, link failed");
+            }
+        }
+
+        void dropRemoteAssociation(int32_t other) {
+            auto&& iter = mOtherList.find(other);
+            if (iter != mOtherList.end()) {
+                iter->second.mLinks.erase(mId);
+                if (iter->second.mLinks.empty()) {
+                    // This only erases them from the map, not from general tracking
+                    mOtherList.erase(iter);
+                }
+            } else {
+                ALOGE("Missing entry in SessionLayerMap, unlinking failed");
+            }
+        }
+
+        int32_t mId;
+        std::set<int> mLinks;
+        std::unordered_map<int32_t, MappedType>& mOtherList;
+    };
+
+    std::unordered_map<int32_t, MappedType> mSessions;
+    std::unordered_map<int32_t, MappedType> mLayers;
+};
+
+} // namespace android::adpf
diff --git a/services/surfaceflinger/PowerAdvisor/SessionManager.cpp b/services/surfaceflinger/PowerAdvisor/SessionManager.cpp
new file mode 100644
index 0000000..a855f07
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/SessionManager.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PowerAdvisor/SessionManager.h"
+#include <android/binder_libbinder.h>
+#include <android/binder_status.h>
+#include <binder/IPCThreadState.h>
+#include "FrontEnd/LayerHandle.h"
+#include "Layer.h"
+#include "SurfaceFlinger.h"
+
+namespace android::adpf {
+
+SessionManager::SessionManager(uid_t uid) : mUid(uid) {}
+
+ndk::ScopedAStatus SessionManager::associateSessionToLayers(
+        int32_t sessionId, int32_t ownerUid, const std::vector<::ndk::SpAIBinder>& layerTokens) {
+    std::scoped_lock lock{mSessionManagerMutex};
+
+    std::vector<int32_t> layerIds;
+
+    for (auto&& token : layerTokens) {
+        auto platformToken = AIBinder_toPlatformBinder(token.get());
+
+        // Get the layer id for it
+        int32_t layerId =
+                static_cast<int32_t>(surfaceflinger::LayerHandle::getLayerId(platformToken));
+        auto&& iter = mTrackedLayerData.find(layerId);
+
+        // Ensure it is being tracked
+        if (iter == mTrackedLayerData.end()) {
+            mTrackedLayerData.emplace(layerId, LayerData{.layerId = layerId});
+        }
+        layerIds.push_back(layerId);
+    }
+
+    // Register the session then track it
+    if (mMap.bindSessionIDToLayers(sessionId, layerIds) &&
+        !mTrackedSessionData.contains(sessionId)) {
+        mTrackedSessionData.emplace(sessionId,
+                                    SessionData{.sessionId = sessionId, .uid = ownerUid});
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus SessionManager::trackedSessionsDied(const std::vector<int32_t>& sessionIds) {
+    std::scoped_lock lock{mSessionManagerMutex};
+    for (int sessionId : sessionIds) {
+        mDeadSessions.push_back(sessionId);
+        mTrackedSessionData.erase(sessionId);
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
+void SessionManager::updateTrackingState(
+        const std::vector<std::pair<uint32_t, std::string>>& handles) {
+    std::scoped_lock lock{mSessionManagerMutex};
+    std::vector<int32_t> deadLayers;
+    for (auto&& handle : handles) {
+        int32_t handleId = static_cast<int32_t>(handle.first);
+        auto it = mTrackedLayerData.find(handleId);
+        if (it != mTrackedLayerData.end()) {
+            // Track any dead layers to remove from the mapping
+            mTrackedLayerData.erase(it);
+            deadLayers.push_back(it->first);
+        }
+    }
+    mMap.notifyLayersDied(deadLayers);
+    mMap.notifySessionsDied(mDeadSessions);
+
+    mDeadSessions.clear();
+    mMap.getCurrentlyRelevantLayers(mCurrentlyRelevantLayers);
+}
+
+bool SessionManager::isLayerRelevant(int32_t layerId) {
+    return mCurrentlyRelevantLayers.contains(layerId);
+}
+
+} // namespace android::adpf
\ No newline at end of file
diff --git a/services/surfaceflinger/PowerAdvisor/SessionManager.h b/services/surfaceflinger/PowerAdvisor/SessionManager.h
new file mode 100644
index 0000000..93a80b5
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/SessionManager.h
@@ -0,0 +1,102 @@
+/*
+ * 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 <aidl/android/adpf/BnSessionManager.h>
+#include <sys/types.h>
+
+#include <utils/Thread.h>
+#include "Common.h"
+#include "SessionLayerMap.h"
+
+#include <string>
+
+namespace android {
+
+class Layer;
+
+namespace adpf {
+namespace impl {
+
+class PowerAdvisor;
+
+}
+
+// Talks to HMS to manage sessions for PowerHAL
+class SessionManager : public BnSessionManager {
+public:
+    SessionManager(uid_t uid);
+
+    // ISessionManager binder methods
+    ndk::ScopedAStatus trackedSessionsDied(const std::vector<int32_t>& in_sessionId) override;
+    ndk::ScopedAStatus associateSessionToLayers(
+            int32_t sessionId, int32_t ownerUid,
+            const std::vector<::ndk::SpAIBinder>& layers) override;
+
+    // Update the lifecycles of any tracked sessions or layers. This is intended to accepts the
+    // "destroyedHandles" object from updateLayerSnapshots in SF, and should reflect that type
+    void updateTrackingState(const std::vector<std::pair<uint32_t, std::string>>& handles);
+
+private:
+    // Session metadata tracked by the mTrackedSessionData map
+    struct SessionData {
+        int32_t sessionId;
+        int uid;
+    };
+
+    // Layer metadata tracked by the mTrackedSessionData map
+    struct LayerData {
+        int32_t layerId;
+    };
+
+    // Checks if the layer is currently associated with a specific session in the SessionLayerMap
+    // This helps us know which layers might be included in an update for the HAL
+    bool isLayerRelevant(int32_t layerId);
+
+    // The UID of whoever created our ISessionManager connection
+    const uid_t mUid;
+
+    // State owned by the main thread
+
+    // Set of layers that are currently being tracked in the SessionLayerMap. This is used to
+    // filter out which layers we actually care about during the latching process
+    std::unordered_set<int32_t> mCurrentlyRelevantLayers;
+
+    // Tracks active associations between sessions and layers. Items in this map can be thought of
+    // as "active" connections, and any session or layer not in this map will not receive updates or
+    // be collected in SurfaceFlinger
+    SessionLayerMap mMap;
+
+    // The list of currently-living layers which have ever been tracked, this is used to persist any
+    // data we want to track across potential mapping disconnects, and to determine when to send
+    // death updates
+    std::unordered_map<int32_t, LayerData> mTrackedLayerData;
+
+    // The list of currently-living sessions which have ever been tracked, this is used to persist
+    // any data we want to track across mapping disconnects
+    std::unordered_map<int32_t, SessionData> mTrackedSessionData;
+
+    // State owned by mSessionManagerMutex
+
+    std::mutex mSessionManagerMutex;
+
+    // The list of sessions that have died since we last called updateTrackingState
+    std::vector<int32_t> mDeadSessions GUARDED_BY(mSessionManagerMutex);
+};
+
+} // namespace adpf
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 21d3396..d3483b0 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -346,7 +346,6 @@
     constexpr bool kRegionSampling = true;
     constexpr bool kGrayscale = false;
     constexpr bool kIsProtected = false;
-    constexpr bool kAttachGainmap = false;
 
     SurfaceFlinger::RenderAreaBuilderVariant
             renderAreaBuilder(std::in_place_type<DisplayRenderAreaBuilder>, sampledBounds,
@@ -358,7 +357,7 @@
             mFlinger.getSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, layers);
     FenceResult fenceResult =
             mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling, kGrayscale,
-                                       kIsProtected, kAttachGainmap, nullptr, displayState, layers)
+                                       kIsProtected, nullptr, displayState, layers)
                     .get();
     if (fenceResult.ok()) {
         fenceResult.value()->waitForever(LOG_TAG);
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 3fdddac..a2cdd46 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -209,6 +209,7 @@
         ftl::FakeGuard guard(kMainThreadContext);
         resyncToHardwareVsyncLocked(id, allowToEnable, modePtr);
     }
+    void resync() override EXCLUDES(mDisplayLock);
     void forceNextResync() { mLastResyncTime = 0; }
 
     // Passes a vsync sample to VsyncController. Returns true if
@@ -470,7 +471,6 @@
     bool throttleVsync(TimePoint, uid_t) override;
     // Get frame interval
     Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
-    void resync() override EXCLUDES(mDisplayLock);
     void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock);
 
     std::unique_ptr<EventThread> mRenderEventThread;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 2147a85..220133d 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -876,6 +876,9 @@
     } else if (algorithm == "kawase2") {
         return renderengine::RenderEngine::BlurAlgorithm::KAWASE_DUAL_FILTER;
     } else {
+        if (FlagManager::getInstance().window_blur_kawase2()) {
+            return renderengine::RenderEngine::BlurAlgorithm::KAWASE_DUAL_FILTER;
+        }
         return renderengine::RenderEngine::BlurAlgorithm::KAWASE;
     }
 }
@@ -1440,14 +1443,14 @@
     return future.get();
 }
 
-void SurfaceFlinger::finalizeDisplayModeChange(PhysicalDisplayId displayId) {
+bool SurfaceFlinger::finalizeDisplayModeChange(PhysicalDisplayId displayId) {
     SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     const auto pendingModeOpt = mDisplayModeController.getPendingMode(displayId);
     if (!pendingModeOpt) {
         // There is no pending mode change. This can happen if the active
         // display changed and the mode change happened on a different display.
-        return;
+        return true;
     }
 
     const auto& activeMode = pendingModeOpt->mode;
@@ -1462,8 +1465,8 @@
         state.physical->activeMode = activeMode.modePtr.get();
         processDisplayChangesLocked();
 
-        // processDisplayChangesLocked will update all necessary components so we're done here.
-        return;
+        // The DisplayDevice has been destroyed, so abort the commit for the now dead FrameTargeter.
+        return false;
     }
 
     mDisplayModeController.finalizeModeChange(displayId, activeMode.modePtr->getId(),
@@ -1474,6 +1477,8 @@
     if (pendingModeOpt->emitEvent) {
         mScheduler->onDisplayModeChanged(displayId, activeMode, /*clearContentRequirements*/ true);
     }
+
+    return true;
 }
 
 void SurfaceFlinger::dropModeRequest(PhysicalDisplayId displayId) {
@@ -2655,7 +2660,10 @@
 
         for (const auto [displayId, _] : frameTargets) {
             if (mDisplayModeController.isModeSetPending(displayId)) {
-                finalizeDisplayModeChange(displayId);
+                if (!finalizeDisplayModeChange(displayId)) {
+                    mScheduler->scheduleFrame();
+                    return false;
+                }
             }
         }
     }
@@ -2784,12 +2792,43 @@
     const auto& displays = FTL_FAKE_GUARD(mStateLock, mDisplays);
     refreshArgs.outputs.reserve(displays.size());
 
+    // Track layer stacks of physical displays that might be added to CompositionEngine
+    // output. Layer stacks are not tracked in Display when we iterate through
+    // frameTargeters. Cross-referencing layer stacks allows us to filter out displays
+    // by ID with duplicate layer stacks before adding them to CompositionEngine output.
+    ui::DisplayMap<DisplayId, ui::LayerStack> physicalDisplayLayerStacks;
+    for (auto& [_, display] : displays) {
+        const auto id = PhysicalDisplayId::tryCast(display->getId());
+        if (id && frameTargeters.contains(*id)) {
+            physicalDisplayLayerStacks.try_emplace(*id, display->getLayerStack());
+        }
+    }
+
+    // Tracks layer stacks of displays that are added to CompositionEngine output.
+    ui::DisplayMap<ui::LayerStack, ftl::Unit> outputLayerStacks;
+    auto isOutputLayerStack = [&outputLayerStacks](DisplayId id, ui::LayerStack layerStack) {
+        if (FlagManager::getInstance().reject_dupe_layerstacks() &&
+            outputLayerStacks.contains(layerStack)) {
+            // TODO: remove log and DisplayId from params once reject_dupe_layerstacks flag is
+            // removed
+            ALOGD("Existing layer stack ID %d output to another display %" PRIu64
+                  ", dropping display from outputs",
+                  layerStack.id, id.value);
+            return true;
+        }
+        outputLayerStacks.try_emplace(layerStack);
+        return false;
+    };
+
     // Add outputs for physical displays.
     for (const auto& [id, targeter] : frameTargeters) {
         ftl::FakeGuard guard(mStateLock);
 
         if (const auto display = getCompositionDisplayLocked(id)) {
-            refreshArgs.outputs.push_back(display);
+            const auto layerStack = physicalDisplayLayerStacks.get(id)->get();
+            if (!isOutputLayerStack(display->getId(), layerStack)) {
+                refreshArgs.outputs.push_back(display);
+            }
         }
 
         refreshArgs.frameTargets.try_emplace(id, &targeter->target());
@@ -2806,7 +2845,9 @@
 
             if (!refreshRate.isValid() ||
                 mScheduler->isVsyncInPhase(pacesetterTarget.frameBeginTime(), refreshRate)) {
-                refreshArgs.outputs.push_back(display->getCompositionDisplay());
+                if (!isOutputLayerStack(display->getId(), display->getLayerStack())) {
+                    refreshArgs.outputs.push_back(display->getCompositionDisplay());
+                }
             }
         }
     }
@@ -3625,10 +3666,13 @@
             deviceProductInfo = snapshot.deviceProductInfo();
         }
 
+        // Use the cached port via snapshot because we are updating an existing
+        // display on reconnect.
         const auto it =
                 mPhysicalDisplays.try_replace(displayId, display.token(), displayId,
-                                              snapshot.connectionType(), std::move(displayModes),
-                                              std::move(colorModes), std::move(deviceProductInfo));
+                                              snapshot.port(), snapshot.connectionType(),
+                                              std::move(displayModes), std::move(colorModes),
+                                              std::move(deviceProductInfo));
 
         auto& state = mCurrentState.displays.editValueFor(it->second.token());
         state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId.
@@ -3641,7 +3685,7 @@
     const ui::DisplayConnectionType connectionType =
             getHwComposer().getDisplayConnectionType(displayId);
 
-    mPhysicalDisplays.try_emplace(displayId, token, displayId, connectionType,
+    mPhysicalDisplays.try_emplace(displayId, token, displayId, info.port, connectionType,
                                   std::move(displayModes), std::move(colorModes),
                                   std::move(info.deviceProductInfo));
 
@@ -4557,6 +4601,7 @@
     SFTRACE_INT("mTransactionFlags", transactionFlags);
 
     if (const bool scheduled = transactionFlags & mask; !scheduled) {
+        mScheduler->resync();
         scheduleCommit(frameHint);
     } else if (frameHint == FrameHint::kActive) {
         // Even if the next frame is already scheduled, we should reset the idle timer
@@ -7139,8 +7184,7 @@
                                                  displayWeak, options),
                         getLayerSnapshotsFn, reqSize,
                         static_cast<ui::PixelFormat>(captureArgs.pixelFormat),
-                        captureArgs.allowProtected, captureArgs.grayscale,
-                        captureArgs.attachGainmap, captureListener);
+                        captureArgs.allowProtected, captureArgs.grayscale, captureListener);
 }
 
 void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args,
@@ -7197,7 +7241,7 @@
                                                  static_cast<ui::Dataspace>(args.dataspace),
                                                  displayWeak, options),
                         getLayerSnapshotsFn, size, static_cast<ui::PixelFormat>(args.pixelFormat),
-                        kAllowProtected, kGrayscale, args.attachGainmap, captureListener);
+                        kAllowProtected, kGrayscale, captureListener);
 }
 
 ScreenCaptureResults SurfaceFlinger::captureLayersSync(const LayerCaptureArgs& args) {
@@ -7308,8 +7352,7 @@
                                                  options),
                         getLayerSnapshotsFn, reqSize,
                         static_cast<ui::PixelFormat>(captureArgs.pixelFormat),
-                        captureArgs.allowProtected, captureArgs.grayscale,
-                        captureArgs.attachGainmap, captureListener);
+                        captureArgs.allowProtected, captureArgs.grayscale, captureListener);
 }
 
 // Creates a Future release fence for a layer and keeps track of it in a list to
@@ -7367,7 +7410,7 @@
 void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuilder,
                                          GetLayerSnapshotsFunction getLayerSnapshotsFn,
                                          ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
-                                         bool allowProtected, bool grayscale, bool attachGainmap,
+                                         bool allowProtected, bool grayscale,
                                          const sp<IScreenCaptureListener>& captureListener) {
     SFTRACE_CALL();
 
@@ -7382,6 +7425,10 @@
     std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
     auto displayState = getSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, layers);
 
+    const bool hasHdrLayer = std::any_of(layers.cbegin(), layers.cend(), [this](const auto& layer) {
+        return isHdrLayer(*(layer.second->mSnapshot.get()));
+    });
+
     const bool supportsProtected = getRenderEngine().supportsProtectedContent();
     bool hasProtectedLayer = false;
     if (allowProtected && supportsProtected) {
@@ -7410,9 +7457,51 @@
             renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
                                                  renderengine::impl::ExternalTexture::Usage::
                                                          WRITEABLE);
-    auto futureFence =
-            captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */, grayscale,
-                              isProtected, attachGainmap, captureListener, displayState, layers);
+
+    std::shared_ptr<renderengine::impl::ExternalTexture> hdrTexture;
+    std::shared_ptr<renderengine::impl::ExternalTexture> gainmapTexture;
+
+    bool hintForSeamless = std::visit(
+            [](auto&& arg) {
+                return arg.options.test(RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION);
+            },
+            renderAreaBuilder);
+    if (hasHdrLayer && !hintForSeamless && FlagManager::getInstance().true_hdr_screenshots()) {
+        const auto hdrBuffer =
+                getFactory().createGraphicBuffer(buffer->getWidth(), buffer->getHeight(),
+                                                 HAL_PIXEL_FORMAT_RGBA_FP16, 1 /* layerCount */,
+                                                 buffer->getUsage(), "screenshot-hdr");
+        const auto gainmapBuffer =
+                getFactory().createGraphicBuffer(buffer->getWidth(), buffer->getHeight(),
+                                                 buffer->getPixelFormat(), 1 /* layerCount */,
+                                                 buffer->getUsage(), "screenshot-gainmap");
+
+        const status_t hdrBufferStatus = hdrBuffer->initCheck();
+        const status_t gainmapBufferStatus = gainmapBuffer->initCheck();
+
+        if (hdrBufferStatus != OK || gainmapBufferStatus != -OK) {
+            if (hdrBufferStatus != OK) {
+                ALOGW("%s: Buffer failed to allocate for hdr: %d. Screenshoting SDR instead.",
+                      __func__, hdrBufferStatus);
+            } else {
+                ALOGW("%s: Buffer failed to allocate for gainmap: %d. Screenshoting SDR instead.",
+                      __func__, gainmapBufferStatus);
+            }
+        } else {
+            hdrTexture = std::make_shared<
+                    renderengine::impl::ExternalTexture>(hdrBuffer, getRenderEngine(),
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::WRITEABLE);
+            gainmapTexture = std::make_shared<
+                    renderengine::impl::ExternalTexture>(gainmapBuffer, getRenderEngine(),
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::WRITEABLE);
+        }
+    }
+
+    auto futureFence = captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */,
+                                         grayscale, isProtected, captureListener, displayState,
+                                         layers, hdrTexture, gainmapTexture);
     futureFence.get();
 }
 
@@ -7455,10 +7544,11 @@
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshot(
         const RenderAreaBuilderVariant& renderAreaBuilder,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, bool attachGainmap,
-        const sp<IScreenCaptureListener>& captureListener,
-        std::optional<OutputCompositionState>& displayState,
-        std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
+        bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener,
+        const std::optional<OutputCompositionState>& displayState,
+        const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers,
+        const std::shared_ptr<renderengine::ExternalTexture>& hdrBuffer,
+        const std::shared_ptr<renderengine::ExternalTexture>& gainmapBuffer) {
     SFTRACE_CALL();
 
     ScreenCaptureResults captureResults;
@@ -7474,74 +7564,40 @@
         }
         return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
     }
+
     float displayBrightnessNits = displayState.value().displayBrightnessNits;
     float sdrWhitePointNits = displayState.value().sdrWhitePointNits;
 
-    ftl::SharedFuture<FenceResult> renderFuture =
-            renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale, isProtected,
-                             captureResults, displayState, layers);
+    ftl::SharedFuture<FenceResult> renderFuture;
 
-    if (captureResults.capturedHdrLayers && attachGainmap &&
-        FlagManager::getInstance().true_hdr_screenshots()) {
-        sp<GraphicBuffer> hdrBuffer =
-                getFactory().createGraphicBuffer(buffer->getWidth(), buffer->getHeight(),
-                                                 HAL_PIXEL_FORMAT_RGBA_FP16, 1 /* layerCount */,
-                                                 buffer->getUsage(), "screenshot-hdr");
-        sp<GraphicBuffer> gainmapBuffer =
-                getFactory().createGraphicBuffer(buffer->getWidth(), buffer->getHeight(),
-                                                 buffer->getPixelFormat(), 1 /* layerCount */,
-                                                 buffer->getUsage(), "screenshot-gainmap");
+    if (hdrBuffer && gainmapBuffer) {
+        ftl::SharedFuture<FenceResult> hdrRenderFuture =
+                renderScreenImpl(renderArea.get(), hdrBuffer, regionSampling, grayscale,
+                                 isProtected, captureResults, displayState, layers);
+        captureResults.buffer = buffer->getBuffer();
+        captureResults.optionalGainMap = gainmapBuffer->getBuffer();
 
-        const status_t bufferStatus = hdrBuffer->initCheck();
-        const status_t gainmapBufferStatus = gainmapBuffer->initCheck();
+        renderFuture =
+                ftl::Future(std::move(hdrRenderFuture))
+                        .then([&, displayBrightnessNits, sdrWhitePointNits,
+                               dataspace = captureResults.capturedDataspace, buffer, hdrBuffer,
+                               gainmapBuffer](FenceResult fenceResult) -> FenceResult {
+                            if (!fenceResult.ok()) {
+                                return fenceResult;
+                            }
 
-        if (bufferStatus != OK) {
-            ALOGW("%s: Buffer failed to allocate for hdr: %d. Screenshoting SDR instead.", __func__,
-                  bufferStatus);
-        } else if (gainmapBufferStatus != OK) {
-            ALOGW("%s: Buffer failed to allocate for gainmap: %d. Screenshoting SDR instead.",
-                  __func__, gainmapBufferStatus);
-        } else {
-            captureResults.optionalGainMap = gainmapBuffer;
-            const auto hdrTexture = std::make_shared<
-                    renderengine::impl::ExternalTexture>(hdrBuffer, getRenderEngine(),
-                                                         renderengine::impl::ExternalTexture::
-                                                                 Usage::WRITEABLE);
-            const auto gainmapTexture = std::make_shared<
-                    renderengine::impl::ExternalTexture>(gainmapBuffer, getRenderEngine(),
-                                                         renderengine::impl::ExternalTexture::
-                                                                 Usage::WRITEABLE);
-            ScreenCaptureResults unusedResults;
-            ftl::SharedFuture<FenceResult> hdrRenderFuture =
-                    renderScreenImpl(renderArea.get(), hdrTexture, regionSampling, grayscale,
-                                     isProtected, unusedResults, displayState, layers);
-
-            renderFuture =
-                    ftl::Future(std::move(renderFuture))
-                            .then([&, hdrRenderFuture = std::move(hdrRenderFuture),
-                                   displayBrightnessNits, sdrWhitePointNits,
-                                   dataspace = captureResults.capturedDataspace, buffer, hdrTexture,
-                                   gainmapTexture](FenceResult fenceResult) -> FenceResult {
-                                if (!fenceResult.ok()) {
-                                    return fenceResult;
-                                }
-
-                                auto hdrFenceResult = hdrRenderFuture.get();
-
-                                if (!hdrFenceResult.ok()) {
-                                    return hdrFenceResult;
-                                }
-
-                                return getRenderEngine()
-                                        .drawGainmap(buffer, fenceResult.value()->get(), hdrTexture,
-                                                     hdrFenceResult.value()->get(),
-                                                     displayBrightnessNits / sdrWhitePointNits,
-                                                     static_cast<ui::Dataspace>(dataspace),
-                                                     gainmapTexture)
-                                        .get();
-                            })
-                            .share();
-        };
+                            return getRenderEngine()
+                                    .tonemapAndDrawGainmap(hdrBuffer, fenceResult.value()->get(),
+                                                           displayBrightnessNits /
+                                                                   sdrWhitePointNits,
+                                                           static_cast<ui::Dataspace>(dataspace),
+                                                           buffer, gainmapBuffer)
+                                    .get();
+                        })
+                        .share();
+    } else {
+        renderFuture = renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale,
+                                        isProtected, captureResults, displayState, layers);
     }
 
     if (captureListener) {
@@ -7563,8 +7619,8 @@
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
         const RenderArea* renderArea, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
         bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
-        std::optional<OutputCompositionState>& displayState,
-        std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
+        const std::optional<OutputCompositionState>& displayState,
+        const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
     SFTRACE_CALL();
 
     for (auto& [_, layerFE] : layers) {
@@ -7632,9 +7688,8 @@
     }
 
     auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace,
-                    sdrWhitePointNits, displayBrightnessNits, grayscale, isProtected,
-                    layers = std::move(layers), layerStack, regionSampling,
-                    renderArea = std::move(renderArea), renderIntent,
+                    sdrWhitePointNits, displayBrightnessNits, grayscale, isProtected, layers,
+                    layerStack, regionSampling, renderArea = std::move(renderArea), renderIntent,
                     enableLocalTonemapping]() -> FenceResult {
         std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
                 mFactory.createCompositionEngine();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 0ae236f..c85c084 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -732,7 +732,11 @@
                                        Fps maxFps);
 
     void initiateDisplayModeChanges() REQUIRES(kMainThreadContext) REQUIRES(mStateLock);
-    void finalizeDisplayModeChange(PhysicalDisplayId) REQUIRES(kMainThreadContext)
+
+    // Returns whether the commit stage should proceed. The return value is ignored when finalizing
+    // immediate mode changes, which happen toward the end of the commit stage.
+    // TODO: b/355427258 - Remove the return value once the `synced_resolution_switch` flag is live.
+    bool finalizeDisplayModeChange(PhysicalDisplayId) REQUIRES(kMainThreadContext)
             REQUIRES(mStateLock);
 
     void dropModeRequest(PhysicalDisplayId) REQUIRES(kMainThreadContext);
@@ -874,7 +878,7 @@
 
     void captureScreenCommon(RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
                              ui::Size bufferSize, ui::PixelFormat, bool allowProtected,
-                             bool grayscale, bool attachGainmap, const sp<IScreenCaptureListener>&);
+                             bool grayscale, const sp<IScreenCaptureListener>&);
 
     std::optional<OutputCompositionState> getDisplayStateFromRenderAreaBuilder(
             RenderAreaBuilderVariant& renderAreaBuilder) REQUIRES(kMainThreadContext);
@@ -882,16 +886,17 @@
     ftl::SharedFuture<FenceResult> captureScreenshot(
             const RenderAreaBuilderVariant& renderAreaBuilder,
             const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-            bool grayscale, bool isProtected, bool attachGainmap,
-            const sp<IScreenCaptureListener>& captureListener,
-            std::optional<OutputCompositionState>& displayState,
-            std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
+            bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener,
+            const std::optional<OutputCompositionState>& displayState,
+            const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers,
+            const std::shared_ptr<renderengine::ExternalTexture>& hdrBuffer = nullptr,
+            const std::shared_ptr<renderengine::ExternalTexture>& gainmapBuffer = nullptr);
 
     ftl::SharedFuture<FenceResult> renderScreenImpl(
             const RenderArea*, const std::shared_ptr<renderengine::ExternalTexture>&,
             bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults&,
-            std::optional<OutputCompositionState>& displayState,
-            std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
+            const std::optional<OutputCompositionState>& displayState,
+            const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
 
     void readPersistentProperties();
 
diff --git a/services/surfaceflinger/Tracing/tools/Android.bp b/services/surfaceflinger/Tracing/tools/Android.bp
index 63c1b37..d2ffcc0 100644
--- a/services/surfaceflinger/Tracing/tools/Android.bp
+++ b/services/surfaceflinger/Tracing/tools/Android.bp
@@ -27,6 +27,7 @@
     defaults: [
         "libsurfaceflinger_mocks_defaults",
         "librenderengine_deps",
+        "poweradvisor_deps",
         "surfaceflinger_defaults",
         "libsurfaceflinger_common_deps",
     ],
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 858f759..e80cd78 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -112,59 +112,63 @@
     DUMP_LEGACY_SERVER_FLAG(use_skia_tracing);
 
     /// Trunk stable server (R/W) flags ///
-    DUMP_ACONFIG_FLAG(refresh_rate_overlay_on_external_display);
     DUMP_ACONFIG_FLAG(adpf_gpu_sf);
     DUMP_ACONFIG_FLAG(adpf_native_session_manager);
     DUMP_ACONFIG_FLAG(adpf_use_fmq_channel);
     DUMP_ACONFIG_FLAG(graphite_renderengine_preview_rollout);
+    DUMP_ACONFIG_FLAG(refresh_rate_overlay_on_external_display);
 
     /// Trunk stable readonly flags ///
-    DUMP_ACONFIG_FLAG(adpf_fmq_sf);
-    DUMP_ACONFIG_FLAG(arr_setframerate_gte_enum);
-    DUMP_ACONFIG_FLAG(connected_display);
-    DUMP_ACONFIG_FLAG(enable_small_area_detection);
-    DUMP_ACONFIG_FLAG(stable_edid_ids);
-    DUMP_ACONFIG_FLAG(frame_rate_category_mrr);
-    DUMP_ACONFIG_FLAG(misc1);
-    DUMP_ACONFIG_FLAG(vrr_config);
-    DUMP_ACONFIG_FLAG(hdcp_level_hal);
-    DUMP_ACONFIG_FLAG(multithreaded_present);
+    /// IMPORTANT - please keep alphabetize to reduce merge conflicts
     DUMP_ACONFIG_FLAG(add_sf_skipped_frames_to_trace);
-    DUMP_ACONFIG_FLAG(use_known_refresh_rate_for_fps_consistency);
-    DUMP_ACONFIG_FLAG(cache_when_source_crop_layer_only_moved);
-    DUMP_ACONFIG_FLAG(enable_fro_dependent_features);
-    DUMP_ACONFIG_FLAG(display_protected);
-    DUMP_ACONFIG_FLAG(fp16_client_target);
-    DUMP_ACONFIG_FLAG(game_default_frame_rate);
-    DUMP_ACONFIG_FLAG(enable_layer_command_batching);
-    DUMP_ACONFIG_FLAG(vulkan_renderengine);
-    DUMP_ACONFIG_FLAG(renderable_buffer_usage);
-    DUMP_ACONFIG_FLAG(vrr_bugfix_24q4);
-    DUMP_ACONFIG_FLAG(vrr_bugfix_dropped_frame);
-    DUMP_ACONFIG_FLAG(restore_blur_step);
-    DUMP_ACONFIG_FLAG(dont_skip_on_early_ro);
-    DUMP_ACONFIG_FLAG(no_vsyncs_on_screen_off);
-    DUMP_ACONFIG_FLAG(protected_if_client);
-    DUMP_ACONFIG_FLAG(idle_screen_refresh_rate_timeout);
-    DUMP_ACONFIG_FLAG(graphite_renderengine);
-    DUMP_ACONFIG_FLAG(filter_frames_before_trace_starts);
-    DUMP_ACONFIG_FLAG(latch_unsignaled_with_auto_refresh_changed);
-    DUMP_ACONFIG_FLAG(deprecate_vsync_sf);
+    DUMP_ACONFIG_FLAG(adpf_fmq_sf);
     DUMP_ACONFIG_FLAG(allow_n_vsyncs_in_targeter);
-    DUMP_ACONFIG_FLAG(detached_mirror);
+    DUMP_ACONFIG_FLAG(arr_setframerate_gte_enum);
+    DUMP_ACONFIG_FLAG(begone_bright_hlg);
+    DUMP_ACONFIG_FLAG(cache_when_source_crop_layer_only_moved);
     DUMP_ACONFIG_FLAG(commit_not_composited);
+    DUMP_ACONFIG_FLAG(connected_display);
+    DUMP_ACONFIG_FLAG(connected_display_hdr);
     DUMP_ACONFIG_FLAG(correct_dpi_with_display_size);
-    DUMP_ACONFIG_FLAG(local_tonemap_screenshots);
-    DUMP_ACONFIG_FLAG(override_trusted_overlay);
+    DUMP_ACONFIG_FLAG(deprecate_frame_tracker);
+    DUMP_ACONFIG_FLAG(deprecate_vsync_sf);
+    DUMP_ACONFIG_FLAG(detached_mirror);
+    DUMP_ACONFIG_FLAG(display_config_error_hal);
+    DUMP_ACONFIG_FLAG(display_protected);
+    DUMP_ACONFIG_FLAG(dont_skip_on_early_ro);
+    DUMP_ACONFIG_FLAG(enable_fro_dependent_features);
+    DUMP_ACONFIG_FLAG(enable_layer_command_batching);
+    DUMP_ACONFIG_FLAG(enable_small_area_detection);
+    DUMP_ACONFIG_FLAG(filter_frames_before_trace_starts);
     DUMP_ACONFIG_FLAG(flush_buffer_slots_to_uncache);
     DUMP_ACONFIG_FLAG(force_compile_graphite_renderengine);
+    DUMP_ACONFIG_FLAG(fp16_client_target);
+    DUMP_ACONFIG_FLAG(frame_rate_category_mrr);
+    DUMP_ACONFIG_FLAG(game_default_frame_rate);
+    DUMP_ACONFIG_FLAG(graphite_renderengine);
+    DUMP_ACONFIG_FLAG(hdcp_level_hal);
+    DUMP_ACONFIG_FLAG(idle_screen_refresh_rate_timeout);
+    DUMP_ACONFIG_FLAG(latch_unsignaled_with_auto_refresh_changed);
+    DUMP_ACONFIG_FLAG(local_tonemap_screenshots);
+    DUMP_ACONFIG_FLAG(misc1);
+    DUMP_ACONFIG_FLAG(multithreaded_present);
+    DUMP_ACONFIG_FLAG(no_vsyncs_on_screen_off);
+    DUMP_ACONFIG_FLAG(override_trusted_overlay);
+    DUMP_ACONFIG_FLAG(protected_if_client);
+    DUMP_ACONFIG_FLAG(reject_dupe_layerstacks);
+    DUMP_ACONFIG_FLAG(renderable_buffer_usage);
+    DUMP_ACONFIG_FLAG(restore_blur_step);
+    DUMP_ACONFIG_FLAG(skip_invisible_windows_in_input);
+    DUMP_ACONFIG_FLAG(stable_edid_ids);
     DUMP_ACONFIG_FLAG(trace_frame_rate_override);
     DUMP_ACONFIG_FLAG(true_hdr_screenshots);
-    DUMP_ACONFIG_FLAG(display_config_error_hal);
-    DUMP_ACONFIG_FLAG(connected_display_hdr);
-    DUMP_ACONFIG_FLAG(deprecate_frame_tracker);
-    DUMP_ACONFIG_FLAG(skip_invisible_windows_in_input);
-    DUMP_ACONFIG_FLAG(begone_bright_hlg);
+    DUMP_ACONFIG_FLAG(use_known_refresh_rate_for_fps_consistency);
+    DUMP_ACONFIG_FLAG(vrr_bugfix_24q4);
+    DUMP_ACONFIG_FLAG(vrr_bugfix_dropped_frame);
+    DUMP_ACONFIG_FLAG(vrr_config);
+    DUMP_ACONFIG_FLAG(vulkan_renderengine);
+    DUMP_ACONFIG_FLAG(window_blur_kawase2);
+    /// IMPORTANT - please keep alphabetize to reduce merge conflicts
 
 #undef DUMP_ACONFIG_FLAG
 #undef DUMP_LEGACY_SERVER_FLAG
@@ -264,6 +268,8 @@
 FLAG_MANAGER_ACONFIG_FLAG(deprecate_frame_tracker, "");
 FLAG_MANAGER_ACONFIG_FLAG(skip_invisible_windows_in_input, "");
 FLAG_MANAGER_ACONFIG_FLAG(begone_bright_hlg, "debug.sf.begone_bright_hlg");
+FLAG_MANAGER_ACONFIG_FLAG(window_blur_kawase2, "");
+FLAG_MANAGER_ACONFIG_FLAG(reject_dupe_layerstacks, "");
 
 /// Trunk stable server (R/W) flags ///
 FLAG_MANAGER_ACONFIG_FLAG(refresh_rate_overlay_on_external_display, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 05af721..c7f97b4 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -48,61 +48,65 @@
     bool use_skia_tracing() const;
 
     /// Trunk stable server (R/W) flags ///
-    bool refresh_rate_overlay_on_external_display() const;
     bool adpf_gpu_sf() const;
-    bool adpf_use_fmq_channel() const;
     bool adpf_native_session_manager() const;
+    bool adpf_use_fmq_channel() const;
     bool adpf_use_fmq_channel_fixed() const;
     bool graphite_renderengine_preview_rollout() const;
+    bool refresh_rate_overlay_on_external_display() const;
 
     /// Trunk stable readonly flags ///
-    bool arr_setframerate_gte_enum() const;
-    bool adpf_fmq_sf() const;
-    bool connected_display() const;
-    bool frame_rate_category_mrr() const;
-    bool enable_small_area_detection() const;
-    bool stable_edid_ids() const;
-    bool misc1() const;
-    bool vrr_config() const;
-    bool hdcp_level_hal() const;
-    bool multithreaded_present() const;
+    /// IMPORTANT - please keep alphabetize to reduce merge conflicts
     bool add_sf_skipped_frames_to_trace() const;
-    bool use_known_refresh_rate_for_fps_consistency() const;
-    bool cache_when_source_crop_layer_only_moved() const;
-    bool enable_fro_dependent_features() const;
-    bool display_protected() const;
-    bool fp16_client_target() const;
-    bool game_default_frame_rate() const;
-    bool enable_layer_command_batching() const;
-    bool vulkan_renderengine() const;
-    bool vrr_bugfix_24q4() const;
-    bool vrr_bugfix_dropped_frame() const;
-    bool renderable_buffer_usage() const;
-    bool restore_blur_step() const;
-    bool dont_skip_on_early_ro() const;
-    bool no_vsyncs_on_screen_off() const;
-    bool protected_if_client() const;
-    bool idle_screen_refresh_rate_timeout() const;
-    bool graphite_renderengine() const;
-    bool filter_frames_before_trace_starts() const;
-    bool latch_unsignaled_with_auto_refresh_changed() const;
-    bool deprecate_vsync_sf() const;
+    bool adpf_fmq_sf() const;
     bool allow_n_vsyncs_in_targeter() const;
-    bool detached_mirror() const;
+    bool arr_setframerate_gte_enum() const;
+    bool begone_bright_hlg() const;
+    bool cache_when_source_crop_layer_only_moved() const;
     bool commit_not_composited() const;
+    bool connected_display() const;
+    bool connected_display_hdr() const;
     bool correct_dpi_with_display_size() const;
-    bool local_tonemap_screenshots() const;
-    bool override_trusted_overlay() const;
+    bool deprecate_frame_tracker() const;
+    bool deprecate_vsync_sf() const;
+    bool detached_mirror() const;
+    bool display_config_error_hal() const;
+    bool display_protected() const;
+    bool dont_skip_on_early_ro() const;
+    bool enable_fro_dependent_features() const;
+    bool enable_layer_command_batching() const;
+    bool enable_small_area_detection() const;
+    bool filter_frames_before_trace_starts() const;
     bool flush_buffer_slots_to_uncache() const;
     bool force_compile_graphite_renderengine() const;
+    bool fp16_client_target() const;
+    bool frame_rate_category_mrr() const;
+    bool game_default_frame_rate() const;
+    bool graphite_renderengine() const;
+    bool hdcp_level_hal() const;
+    bool idle_screen_refresh_rate_timeout() const;
+    bool latch_unsignaled_with_auto_refresh_changed() const;
+    bool local_tonemap_screenshots() const;
+    bool luts_api() const;
+    bool misc1() const;
+    bool multithreaded_present() const;
+    bool no_vsyncs_on_screen_off() const;
+    bool override_trusted_overlay() const;
+    bool protected_if_client() const;
+    bool reject_dupe_layerstacks() const;
+    bool renderable_buffer_usage() const;
+    bool restore_blur_step() const;
+    bool skip_invisible_windows_in_input() const;
+    bool stable_edid_ids() const;
     bool trace_frame_rate_override() const;
     bool true_hdr_screenshots() const;
-    bool display_config_error_hal() const;
-    bool connected_display_hdr() const;
-    bool deprecate_frame_tracker() const;
-    bool skip_invisible_windows_in_input() const;
-    bool begone_bright_hlg() const;
-    bool luts_api() const;
+    bool use_known_refresh_rate_for_fps_consistency() const;
+    bool vrr_bugfix_24q4() const;
+    bool vrr_bugfix_dropped_frame() const;
+    bool vrr_config() const;
+    bool vulkan_renderengine() const;
+    bool window_blur_kawase2() const;
+    /// IMPORTANT - please keep alphabetize to reduce merge conflicts
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index fc0fabd..b28d269 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -217,6 +217,17 @@
 } # no_vsyncs_on_screen_off
 
 flag {
+  name: "reject_dupe_layerstacks"
+  namespace: "window_surfaces"
+  description: "Reject duplicate layerstacks for displays"
+  bug: "370358572"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+ } # reject_dupe_layerstacks
+
+flag {
   name: "single_hop_screenshot"
   namespace: "window_surfaces"
   description: "Only access SF main thread once during a screenshot"
@@ -295,4 +306,11 @@
   }
 } # vrr_bugfix_dropped_frame
 
+flag {
+  name: "window_blur_kawase2"
+  namespace: "core_graphics"
+  description: "Flag for using Kawase2 algorithm for window blur"
+  bug: "353826438"
+} # window_blur_kawase2
+
 # IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 4d5c0fd..b5f7a74 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -52,7 +52,7 @@
         "LayerTypeTransaction_test.cpp",
         "LayerUpdate_test.cpp",
         "MirrorLayer_test.cpp",
-        "MultiDisplayLayerBounds_test.cpp",
+        "MultiDisplay_test.cpp",
         "RefreshRateOverlay_test.cpp",
         "RelativeZ_test.cpp",
         "ReleaseBufferCallback_test.cpp",
diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplay_test.cpp
similarity index 69%
rename from services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
rename to services/surfaceflinger/tests/MultiDisplay_test.cpp
index 56cf13d..54bb253 100644
--- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
+++ b/services/surfaceflinger/tests/MultiDisplay_test.cpp
@@ -19,6 +19,7 @@
 #pragma clang diagnostic ignored "-Wconversion"
 
 #include <common/FlagManager.h>
+#include <gui/IConsumerListener.h>
 #include <ui/DisplayState.h>
 
 #include "LayerTransactionTest.h"
@@ -32,7 +33,7 @@
 ::testing::Environment* const binderEnv =
         ::testing::AddGlobalTestEnvironment(new BinderEnvironment());
 
-class MultiDisplayLayerBoundsTest : public LayerTransactionTest {
+class MultiDisplayTest : public LayerTransactionTest {
 protected:
     virtual void SetUp() {
         LayerTransactionTest::SetUp();
@@ -45,11 +46,17 @@
         SurfaceComposerClient::getDisplayState(mMainDisplay, &mMainDisplayState);
         SurfaceComposerClient::getActiveDisplayMode(mMainDisplay, &mMainDisplayMode);
 
-        sp<IGraphicBufferConsumer> consumer;
-        BufferQueue::createBufferQueue(&mProducer, &consumer);
-        consumer->setConsumerName(String8("Virtual disp consumer"));
-        consumer->setDefaultBufferSize(mMainDisplayMode.resolution.getWidth(),
-                                       mMainDisplayMode.resolution.getHeight());
+        BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+        mConsumer->setConsumerName(String8("Virtual disp consumer (MultiDisplayLayerBounds)"));
+        mConsumer->setDefaultBufferSize(mMainDisplayMode.resolution.getWidth(),
+                                        mMainDisplayMode.resolution.getHeight());
+
+        class StubConsumerListener : public BnConsumerListener {
+            virtual void onFrameAvailable(const BufferItem&) override {}
+            virtual void onBuffersReleased() override {}
+            virtual void onSidebandStreamChanged() override {}
+        };
+        mConsumer->consumerConnect(sp<StubConsumerListener>::make(), true);
     }
 
     virtual void TearDown() {
@@ -92,12 +99,13 @@
     sp<IBinder> mMainDisplay;
     PhysicalDisplayId mMainDisplayId;
     sp<IBinder> mVirtualDisplay;
+    sp<IGraphicBufferConsumer> mConsumer;
     sp<IGraphicBufferProducer> mProducer;
     sp<SurfaceControl> mColorLayer;
     Color mExpectedColor = {63, 63, 195, 255};
 };
 
-TEST_F(MultiDisplayLayerBoundsTest, RenderLayerInVirtualDisplay) {
+TEST_F(MultiDisplayTest, RenderLayerInVirtualDisplay) {
     constexpr ui::LayerStack kLayerStack{1u};
     createDisplay(mMainDisplayState.layerStackSpaceRect, kLayerStack);
     createColorLayer(kLayerStack);
@@ -116,7 +124,7 @@
     sc->expectColor(Rect(1, 1, 9, 9), {0, 0, 0, 255});
 }
 
-TEST_F(MultiDisplayLayerBoundsTest, RenderLayerInMirroredVirtualDisplay) {
+TEST_F(MultiDisplayTest, RenderLayerInMirroredVirtualDisplay) {
     // Create a display and set its layer stack to the main display's layer stack so
     // the contents of the main display are mirrored on to the virtual display.
 
@@ -142,7 +150,7 @@
     sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
 }
 
-TEST_F(MultiDisplayLayerBoundsTest, RenderLayerWithPromisedFenceInMirroredVirtualDisplay) {
+TEST_F(MultiDisplayTest, RenderLayerWithPromisedFenceInMirroredVirtualDisplay) {
     // Create a display and use a unique layerstack ID for mirrorDisplay() so
     // the contents of the main display are mirrored on to the virtual display.
 
@@ -173,6 +181,51 @@
     sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+TEST_F(MultiDisplayTest, rejectDuplicateLayerStacks) {
+    if (!FlagManager::getInstance().reject_dupe_layerstacks()) return;
+
+    // Setup
+    sp<CpuConsumer> cpuConsumer1 = sp<CpuConsumer>::make(static_cast<size_t>(1));
+    cpuConsumer1->setName(String8("consumer 1"));
+    cpuConsumer1->setDefaultBufferSize(100, 100);
+    sp<IGraphicBufferProducer> cpuProducer1 =
+            cpuConsumer1->getSurface()->getIGraphicBufferProducer();
+    CpuConsumer::LockedBuffer buffer1;
+
+    sp<CpuConsumer> cpuConsumer2 = sp<CpuConsumer>::make(static_cast<size_t>(1));
+    cpuConsumer2->setName(String8("consumer 2"));
+    cpuConsumer2->setDefaultBufferSize(100, 100);
+    sp<IGraphicBufferProducer> cpuProducer2 =
+            cpuConsumer2->getSurface()->getIGraphicBufferProducer();
+    CpuConsumer::LockedBuffer buffer2;
+
+    SurfaceComposerClient::Transaction t;
+    constexpr ui::LayerStack layerStack = {123u};
+    createColorLayer(layerStack);
+
+    static const std::string kDisplayName1("VirtualDisplay1 - rejectDuplicateLayerStacks");
+    sp<IBinder> virtualDisplay1 =
+            SurfaceComposerClient::createVirtualDisplay(kDisplayName1, false /*isSecure*/);
+
+    t.setDisplaySurface(virtualDisplay1, cpuProducer1);
+    t.setDisplayLayerStack(virtualDisplay1, layerStack);
+    t.apply(true);
+
+    static const std::string kDisplayName2("VirtualDisplay2 - rejectDuplicateLayerStacks");
+    sp<IBinder> virtualDisplay2 =
+            SurfaceComposerClient::createVirtualDisplay(kDisplayName2, false /*isSecure*/);
+
+    t.setDisplaySurface(virtualDisplay2, cpuProducer2);
+    t.setDisplayLayerStack(virtualDisplay2, layerStack);
+    t.apply(true);
+
+    // The second consumer will not be able to lock a buffer because
+    // the duplicate layer stack should be rejected.
+    ASSERT_EQ(NO_ERROR, cpuConsumer1->lockNextBuffer(&buffer1));
+    ASSERT_NE(NO_ERROR, cpuConsumer2->lockNextBuffer(&buffer2));
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h
index bf5957a..c95c875 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -58,7 +58,7 @@
                         GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_SW_READ_OFTEN);
                 sp<BufferListener> listener = sp<BufferListener>::make(this);
                 itemConsumer->setFrameAvailableListener(listener);
-                itemConsumer->setName(String8("Virtual disp consumer"));
+                itemConsumer->setName(String8("Virtual disp consumer (TransactionTest)"));
                 itemConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
 #else
                 sp<IGraphicBufferProducer> producer;
@@ -66,7 +66,7 @@
                 sp<BufferItemConsumer> itemConsumer;
                 BufferQueue::createBufferQueue(&producer, &consumer);
 
-                consumer->setConsumerName(String8("Virtual disp consumer"));
+                consumer->setConsumerName(String8("Virtual disp consumer (TransactionTest)"));
                 consumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
 
                 itemConsumer = sp<BufferItemConsumer>::make(consumer,
diff --git a/services/surfaceflinger/tests/VirtualDisplay_test.cpp b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
index d69378c..1108c7f 100644
--- a/services/surfaceflinger/tests/VirtualDisplay_test.cpp
+++ b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
@@ -29,14 +29,14 @@
     void SetUp() override {
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
         mGLConsumer = sp<GLConsumer>::make(GLConsumer::TEXTURE_EXTERNAL, true, false, false);
-        mGLConsumer->setName(String8("Virtual disp consumer"));
+        mGLConsumer->setName(String8("Virtual disp consumer (VirtualDisplayTest)"));
         mGLConsumer->setDefaultBufferSize(100, 100);
         mProducer = mGLConsumer->getSurface()->getIGraphicBufferProducer();
 #else
         sp<IGraphicBufferConsumer> consumer;
 
         BufferQueue::createBufferQueue(&mProducer, &consumer);
-        consumer->setConsumerName(String8("Virtual disp consumer"));
+        consumer->setConsumerName(String8("Virtual disp consumer (VirtualDisplayTest)"));
         consumer->setDefaultBufferSize(100, 100);
 
         mGLConsumer = sp<GLConsumer>::make(consumer, GLConsumer::TEXTURE_EXTERNAL, true, false);
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 6af5143..42259af 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -103,6 +103,7 @@
         "librenderengine_deps",
         "libsurfaceflinger_common_test_deps",
         "libsurfaceflinger_proto_deps",
+        "poweradvisor_deps",
     ],
     static_libs: [
         "android.hardware.common-V2-ndk",
diff --git a/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
index b517ff0..c858d9a 100644
--- a/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
+++ b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
@@ -54,8 +54,8 @@
                 compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
                                                        std::move(compostionEngineDisplayArgs));
         mDisplay = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
-                                             ui::DisplayConnectionType::Internal, HWC_DISPLAY,
-                                             kIsPrimary)
+                                             ui::DisplayConnectionType::Internal,
+                                             DEFAULT_DISPLAY_PORT, HWC_DISPLAY, kIsPrimary)
                            .setDisplaySurface(mDisplaySurface)
                            .setNativeWindow(mNativeWindow)
                            .setPowerMode(hal::PowerMode::ON)
@@ -68,7 +68,9 @@
     using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 
     static constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
-    static constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+    static constexpr uint8_t DEFAULT_DISPLAY_PORT = 42u;
+    static constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID =
+            PhysicalDisplayId::fromPort(DEFAULT_DISPLAY_PORT);
     static constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
     static constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
 
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 860ad2e..71cafbf 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -76,7 +76,8 @@
 constexpr hal::HWLayerId HWC_LAYER = 5000;
 constexpr Transform DEFAULT_TRANSFORM = static_cast<Transform>(0);
 
-constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+constexpr uint8_t DEFAULT_DISPLAY_PORT = 42u;
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(DEFAULT_DISPLAY_PORT);
 constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
 constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
 
@@ -276,7 +277,8 @@
 
         test->mDisplay =
                 FakeDisplayDeviceInjector(test->mFlinger, compositionDisplay,
-                                          kDisplayConnectionType, HWC_DISPLAY, kIsPrimary)
+                                          kDisplayConnectionType, DEFAULT_DISPLAY_PORT, HWC_DISPLAY,
+                                          kIsPrimary)
                         .setDisplaySurface(test->mDisplaySurface)
                         .setNativeWindow(test->mNativeWindow)
                         .setSecure(Derived::IS_SECURE)
diff --git a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
index 29a1fab..c6cbe52 100644
--- a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
@@ -71,7 +71,7 @@
         ASSERT_TRUE(infoOpt);
 
         mDisplayId = infoOpt->id;
-        mDisplaySnapshotOpt.emplace(mDisplayId, ui::DisplayConnectionType::Internal,
+        mDisplaySnapshotOpt.emplace(mDisplayId, infoOpt->port, ui::DisplayConnectionType::Internal,
                                     makeModes(kMode60, kMode90, kMode120), ui::ColorModes{},
                                     std::nullopt);
 
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index fa976c8..6e231aa 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -231,6 +231,16 @@
     static constexpr std::optional<HWDisplayId> value = PhysicalDisplay::HWC_DISPLAY_ID;
 };
 
+template <typename>
+struct PortGetter {
+    static constexpr std::optional<uint8_t> value;
+};
+
+template <typename PhysicalDisplay>
+struct PortGetter<PhysicalDisplayIdType<PhysicalDisplay>> {
+    static constexpr std::optional<uint8_t> value = PhysicalDisplay::PORT;
+};
+
 // DisplayIdType can be:
 //     1) PhysicalDisplayIdType<...> for generated ID of physical display backed by HWC.
 //     2) HalVirtualDisplayIdType<...> for hard-coded ID of virtual display backed by HWC.
@@ -241,6 +251,7 @@
     using DISPLAY_ID = DisplayIdGetter<DisplayIdType>;
     using CONNECTION_TYPE = DisplayConnectionTypeGetter<DisplayIdType>;
     using HWC_DISPLAY_ID_OPT = HwcDisplayIdGetter<DisplayIdType>;
+    using PORT = PortGetter<DisplayIdType>;
 
     static constexpr int WIDTH = width;
     static constexpr int HEIGHT = height;
@@ -277,6 +288,7 @@
                 TestableSurfaceFlinger::FakeDisplayDeviceInjector(test->mFlinger,
                                                                   compositionDisplay,
                                                                   CONNECTION_TYPE::value,
+                                                                  PORT::value,
                                                                   HWC_DISPLAY_ID_OPT::value,
                                                                   static_cast<bool>(PRIMARY));
 
@@ -558,6 +570,10 @@
                                                 /*hasIdentificationData=*/true, kNonSecure>,
                                1080, 2092>;
 
+using ExternalDisplayWithIdentificationVariant =
+        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External,
+                                                /*hasIdentificationData=*/true, kNonSecure>,
+                               1920, 1280>;
 using ExternalDisplayVariant =
         PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External,
                                                 /*hasIdentificationData=*/false, kSecure>,
diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
index 744c536..5f7a9f2 100644
--- a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
+++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
@@ -24,12 +24,15 @@
 
 namespace android {
 
+static constexpr uint8_t kDefaultPort = 255u;
+
 using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 using android::adpf::mock::PowerAdvisor;
 using android::hardware::graphics::composer::hal::HWDisplayId;
 
 struct FakeDisplayInjectorArgs {
-    PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(255u);
+    PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(kDefaultPort);
+    uint8_t port = kDefaultPort;
     HWDisplayId hwcDisplayId = 0;
     bool isPrimary = true;
 };
@@ -73,7 +76,7 @@
                                       .build());
 
         auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
-                                                  ui::DisplayConnectionType::Internal,
+                                                  ui::DisplayConnectionType::Internal, args.port,
                                                   args.hwcDisplayId, args.isPrimary);
 
         injector.setNativeWindow(mNativeWindow);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index b0dd5c2..a506873 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -126,8 +126,9 @@
 
     static constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID;
     static constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1;
-
-    static constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u);
+    static constexpr uint8_t kOuterDisplayPort = 254u;
+    static constexpr PhysicalDisplayId kOuterDisplayId =
+            PhysicalDisplayId::fromPort(kOuterDisplayPort);
 
     auto injectOuterDisplay() {
         // For the inner display, this is handled by setupHwcHotplugCallExpectations.
@@ -149,6 +150,7 @@
                                              kModeId120);
                 },
                 {.displayId = kOuterDisplayId,
+                 .port = kOuterDisplayPort,
                  .hwcDisplayId = kOuterDisplayHwcId,
                  .isPrimary = kIsPrimary});
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index aef467a..4e7a174 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -59,6 +59,137 @@
     EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
 }
 
+TEST_F(HotplugTest, createsDisplaySnapshotsForDisplaysWithIdentificationData) {
+    // Configure a primary display with identification data.
+    using PrimaryDisplay = InnerDisplayVariant;
+    PrimaryDisplay::setupHwcHotplugCallExpectations(this);
+    PrimaryDisplay::setupHwcGetActiveConfigCallExpectations(this);
+    PrimaryDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+
+    // TODO(b/241286146): Remove this unnecessary call.
+    EXPECT_CALL(*mComposer,
+                setVsyncEnabled(PrimaryDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
+            .WillOnce(Return(Error::NONE));
+
+    // A single commit should be scheduled.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
+
+    mFlinger.configure();
+
+    // Configure an external display with identification info.
+    using ExternalDisplay = ExternalDisplayWithIdentificationVariant;
+    ExternalDisplay::setupHwcHotplugCallExpectations(this);
+    ExternalDisplay::setupHwcGetActiveConfigCallExpectations(this);
+    ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+
+    // TODO(b/241286146): Remove this unnecessary call.
+    EXPECT_CALL(*mComposer,
+                setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
+            .WillOnce(Return(Error::NONE));
+
+    mFlinger.configure();
+
+    EXPECT_TRUE(hasPhysicalHwcDisplay(PrimaryDisplay::HWC_DISPLAY_ID));
+    EXPECT_TRUE(mFlinger.getHwComposer().isConnected(PrimaryDisplay::DISPLAY_ID::get()));
+    const auto primaryDisplayIdOpt =
+            mFlinger.getHwComposer().toPhysicalDisplayId(PrimaryDisplay::HWC_DISPLAY_ID);
+    ASSERT_TRUE(primaryDisplayIdOpt.has_value());
+    const auto primaryPhysicalDisplayOpt =
+            mFlinger.physicalDisplays().get(primaryDisplayIdOpt.value());
+    ASSERT_TRUE(primaryPhysicalDisplayOpt.has_value());
+    const auto primaryDisplaySnapshotRef = primaryPhysicalDisplayOpt->get().snapshotRef();
+    EXPECT_EQ(PrimaryDisplay::DISPLAY_ID::get(), primaryDisplaySnapshotRef.get().displayId());
+    EXPECT_EQ(PrimaryDisplay::PORT::value, primaryDisplaySnapshotRef.get().port());
+    EXPECT_EQ(PrimaryDisplay::CONNECTION_TYPE::value,
+              primaryDisplaySnapshotRef.get().connectionType());
+
+    EXPECT_TRUE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
+    EXPECT_TRUE(mFlinger.getHwComposer().isConnected(ExternalDisplay::DISPLAY_ID::get()));
+    const auto externalDisplayIdOpt =
+            mFlinger.getHwComposer().toPhysicalDisplayId(ExternalDisplay::HWC_DISPLAY_ID);
+    ASSERT_TRUE(externalDisplayIdOpt.has_value());
+    const auto externalPhysicalDisplayOpt =
+            mFlinger.physicalDisplays().get(externalDisplayIdOpt.value());
+    ASSERT_TRUE(externalPhysicalDisplayOpt.has_value());
+    const auto externalDisplaySnapshotRef = externalPhysicalDisplayOpt->get().snapshotRef();
+    EXPECT_EQ(ExternalDisplay::DISPLAY_ID::get(), externalDisplaySnapshotRef.get().displayId());
+    EXPECT_EQ(ExternalDisplay::PORT::value, externalDisplaySnapshotRef.get().port());
+    EXPECT_EQ(ExternalDisplay::CONNECTION_TYPE::value,
+              externalDisplaySnapshotRef.get().connectionType());
+}
+
+TEST_F(HotplugTest, createsDisplaySnapshotsForDisplaysWithoutIdentificationData) {
+    // Configure a primary display without identification data.
+    using PrimaryDisplay = PrimaryDisplayVariant;
+    PrimaryDisplay::setupHwcHotplugCallExpectations(this);
+    PrimaryDisplay::setupHwcGetActiveConfigCallExpectations(this);
+    PrimaryDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+
+    // TODO(b/241286146): Remove this unnecessary call.
+    EXPECT_CALL(*mComposer,
+                setVsyncEnabled(PrimaryDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
+            .WillOnce(Return(Error::NONE));
+
+    // A single commit should be scheduled.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
+
+    mFlinger.configure();
+
+    // Configure an external display with identification info.
+    using ExternalDisplay = ExternalDisplayWithIdentificationVariant;
+    ExternalDisplay::setupHwcHotplugCallExpectations(this);
+    ExternalDisplay::setupHwcGetActiveConfigCallExpectations(this);
+    ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+
+    // TODO(b/241286146): Remove this unnecessary call.
+    EXPECT_CALL(*mComposer,
+                setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
+            .WillOnce(Return(Error::NONE));
+
+    mFlinger.configure();
+
+    // Both ID and port are expected to be equal to 0 for primary internal display due to no
+    // identification data.
+    constexpr uint8_t primaryInternalDisplayPort = 0u;
+    constexpr PhysicalDisplayId primaryInternalDisplayId =
+            PhysicalDisplayId::fromPort(primaryInternalDisplayPort);
+    EXPECT_TRUE(hasPhysicalHwcDisplay(PrimaryDisplay::HWC_DISPLAY_ID));
+    ASSERT_EQ(primaryInternalDisplayId, PrimaryDisplay::DISPLAY_ID::get());
+    EXPECT_TRUE(mFlinger.getHwComposer().isConnected(PrimaryDisplay::DISPLAY_ID::get()));
+    const auto primaryDisplayIdOpt =
+            mFlinger.getHwComposer().toPhysicalDisplayId(PrimaryDisplay::HWC_DISPLAY_ID);
+    ASSERT_TRUE(primaryDisplayIdOpt.has_value());
+    const auto primaryPhysicalDisplayOpt =
+            mFlinger.physicalDisplays().get(primaryDisplayIdOpt.value());
+    ASSERT_TRUE(primaryPhysicalDisplayOpt.has_value());
+    const auto primaryDisplaySnapshotRef = primaryPhysicalDisplayOpt->get().snapshotRef();
+    EXPECT_EQ(primaryInternalDisplayId, primaryDisplaySnapshotRef.get().displayId());
+    EXPECT_EQ(primaryInternalDisplayPort, primaryDisplaySnapshotRef.get().port());
+    EXPECT_EQ(PrimaryDisplay::CONNECTION_TYPE::value,
+              primaryDisplaySnapshotRef.get().connectionType());
+
+    // Even though the external display has identification data available, the lack of data for the
+    // internal display has set of the legacy multi-display mode in SF and therefore the external
+    // display's identification data will be ignored.
+    // Both ID and port are expected to be equal to 1 for external internal display.
+    constexpr uint8_t externalDisplayPort = 1u;
+    constexpr PhysicalDisplayId externalDisplayId =
+            PhysicalDisplayId::fromPort(externalDisplayPort);
+    EXPECT_TRUE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
+    EXPECT_TRUE(mFlinger.getHwComposer().isConnected(externalDisplayId));
+    const auto externalDisplayIdOpt =
+            mFlinger.getHwComposer().toPhysicalDisplayId(ExternalDisplay::HWC_DISPLAY_ID);
+    ASSERT_TRUE(externalDisplayIdOpt.has_value());
+    const auto externalPhysicalDisplayOpt =
+            mFlinger.physicalDisplays().get(externalDisplayIdOpt.value());
+    ASSERT_TRUE(externalPhysicalDisplayOpt.has_value());
+    const auto externalDisplaySnapshotRef = externalPhysicalDisplayOpt->get().snapshotRef();
+    EXPECT_EQ(externalDisplayId, externalDisplaySnapshotRef.get().displayId());
+    EXPECT_EQ(externalDisplayPort, externalDisplaySnapshotRef.get().port());
+    EXPECT_EQ(ExternalDisplay::CONNECTION_TYPE::value,
+              externalDisplaySnapshotRef.get().connectionType());
+}
+
 TEST_F(HotplugTest, ignoresDuplicateDisconnection) {
     // Inject a primary display.
     PrimaryDisplayVariant::injectHwcDisplay(this);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index 352000e..6951eaf 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -239,6 +239,8 @@
         ASSERT_TRUE(displayId);
         const auto hwcDisplayId = Case::Display::HWC_DISPLAY_ID_OPT::value;
         ASSERT_TRUE(hwcDisplayId);
+        const auto port = Case::Display::PORT::value;
+        ASSERT_TRUE(port);
         mFlinger.getHwComposer().allocatePhysicalDisplay(*hwcDisplayId, *displayId, std::nullopt);
         DisplayModePtr activeMode = DisplayMode::Builder(Case::Display::HWC_ACTIVE_CONFIG_ID)
                                             .setResolution(Case::Display::RESOLUTION)
@@ -258,7 +260,7 @@
         }
 
         const auto it = mFlinger.mutablePhysicalDisplays()
-                                .emplace_or_replace(*displayId, displayToken, *displayId,
+                                .emplace_or_replace(*displayId, displayToken, *displayId, *port,
                                                     *kConnectionTypeOpt, makeModes(activeMode),
                                                     std::move(colorModes), std::nullopt)
                                 .first;
@@ -299,6 +301,13 @@
                   mFlinger.mutableDisplayModeController()
                           .getActiveMode(device->getPhysicalId())
                           .modePtr->getHwcId());
+
+        EXPECT_EQ(Case::Display::PORT::value,
+                  mFlinger.physicalDisplays()
+                          .get(device->getPhysicalId())
+                          .transform([](const display::PhysicalDisplay& display) {
+                              return display.snapshot().port();
+                          }));
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 7f0b7a6..bd1382e 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -947,11 +947,13 @@
         FakeDisplayDeviceInjector(TestableSurfaceFlinger& flinger,
                                   std::shared_ptr<compositionengine::Display> display,
                                   std::optional<ui::DisplayConnectionType> connectionType,
+                                  std::optional<uint8_t> port,
                                   std::optional<hal::HWDisplayId> hwcDisplayId, bool isPrimary)
               : mFlinger(flinger),
                 mCreationArgs(flinger.mFlinger, flinger.mFlinger->getHwComposer(), mDisplayToken,
                               display),
                 mConnectionType(connectionType),
+                mPort(port),
                 mHwcDisplayId(hwcDisplayId) {
             mCreationArgs.isPrimary = isPrimary;
             mCreationArgs.initialPowerMode = hal::PowerMode::ON;
@@ -1100,11 +1102,12 @@
                                   .hwcDisplayId = *mHwcDisplayId,
                                   .activeMode = activeModeOpt->get()};
 
-                const auto it = mFlinger.mutablePhysicalDisplays()
-                                        .emplace_or_replace(*physicalId, mDisplayToken, *physicalId,
-                                                            *mConnectionType, std::move(modes),
-                                                            ui::ColorModes(), std::nullopt)
-                                        .first;
+                const auto it =
+                        mFlinger.mutablePhysicalDisplays()
+                                .emplace_or_replace(*physicalId, mDisplayToken, *physicalId, *mPort,
+                                                    *mConnectionType, std::move(modes),
+                                                    ui::ColorModes(), std::nullopt)
+                                .first;
 
                 mFlinger.mutableDisplayModeController()
                         .registerDisplay(*physicalId, it->second.snapshot(),
@@ -1138,6 +1141,7 @@
         DisplayModeId mActiveModeId;
         bool mSchedulerRegistration = true;
         const std::optional<ui::DisplayConnectionType> mConnectionType;
+        const std::optional<uint8_t> mPort;
         const std::optional<hal::HWDisplayId> mHwcDisplayId;
     };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerAdvisor.h
index 5c4512a..fd55597 100644
--- a/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerAdvisor.h
@@ -63,6 +63,8 @@
     MOCK_METHOD(void, setCompositeEnd, (TimePoint compositeEndTime), (override));
     MOCK_METHOD(void, setDisplays, (std::vector<DisplayId> & displayIds), (override));
     MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (Duration targetDuration), (override));
+    MOCK_METHOD(std::shared_ptr<SessionManager>, getSessionManager, (), (override));
+    MOCK_METHOD(sp<IBinder>, getOrCreateSessionManagerForBinder, (uid_t uid), (override));
 };
 
 } // namespace android::adpf::mock
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index 9b508aa..3cb9405 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -400,6 +400,78 @@
   }
 };
 
+template <>
+struct EnumTraits<VkPipelineRobustnessBufferBehavior> {
+  static bool exist(uint32_t e) {
+    switch (e) {
+      case VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT:
+      case VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DISABLED:
+      case VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS:
+      case VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_2:
+        return true;
+    }
+    return false;
+  }
+};
+
+template <>
+struct EnumTraits<VkPipelineRobustnessImageBehavior> {
+  static bool exist(uint32_t e) {
+    switch (e) {
+      case VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_DEVICE_DEFAULT:
+      case VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_DISABLED:
+      case VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_ROBUST_IMAGE_ACCESS:
+      case VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_ROBUST_IMAGE_ACCESS_2:
+        return true;
+    }
+    return false;
+  }
+};
+
+template <>
+struct EnumTraits<VkImageLayout> {
+  static bool exist(uint32_t e) {
+    switch (e) {
+      case VK_IMAGE_LAYOUT_UNDEFINED:
+      case VK_IMAGE_LAYOUT_GENERAL:
+      case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
+      case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
+      case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
+      case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
+      case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
+      case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+      case VK_IMAGE_LAYOUT_PREINITIALIZED:
+      case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL:
+      case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL:
+      case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
+      case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL:
+      case VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL:
+      case VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL:
+      case VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL:
+      case VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL:
+      case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
+      case VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR:
+      case VK_IMAGE_LAYOUT_VIDEO_DECODE_SRC_KHR:
+      case VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR:
+      case VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR:
+      case VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT:
+      case VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR:
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+      case VK_IMAGE_LAYOUT_VIDEO_ENCODE_DST_KHR:
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+      case VK_IMAGE_LAYOUT_VIDEO_ENCODE_SRC_KHR:
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+      case VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR:
+#endif
+      case VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT:
+        return true;
+    }
+    return false;
+  }
+};
+
 // VkSparseImageFormatProperties
 
 template <typename Visitor>
@@ -810,6 +882,68 @@
     visitor->Visit("shaderIntegerDotProduct", &features->shaderIntegerDotProduct) &&
     visitor->Visit("maintenance4", &features->maintenance4);
 }
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkJsonCore14* core) {
+  return
+    visitor->Visit("features", &core->features) &&
+    visitor->Visit("properties", &core->properties);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan14Properties* properties) {
+  return
+    visitor->Visit("lineSubPixelPrecisionBits", &properties->lineSubPixelPrecisionBits) &&
+    visitor->Visit("maxVertexAttribDivisor", &properties->maxVertexAttribDivisor) &&
+    visitor->Visit("supportsNonZeroFirstInstance", &properties->supportsNonZeroFirstInstance) &&
+    visitor->Visit("maxPushDescriptors", &properties->maxPushDescriptors) &&
+    visitor->Visit("dynamicRenderingLocalReadDepthStencilAttachments", &properties->dynamicRenderingLocalReadDepthStencilAttachments) &&
+    visitor->Visit("dynamicRenderingLocalReadMultisampledAttachments", &properties->dynamicRenderingLocalReadMultisampledAttachments) &&
+    visitor->Visit("earlyFragmentMultisampleCoverageAfterSampleCounting", &properties->earlyFragmentMultisampleCoverageAfterSampleCounting) &&
+    visitor->Visit("earlyFragmentSampleMaskTestBeforeSampleCounting", &properties->earlyFragmentSampleMaskTestBeforeSampleCounting) &&
+    visitor->Visit("depthStencilSwizzleOneSupport", &properties->depthStencilSwizzleOneSupport) &&
+    visitor->Visit("polygonModePointSize", &properties->polygonModePointSize) &&
+    visitor->Visit("nonStrictSinglePixelWideLinesUseParallelogram", &properties->nonStrictSinglePixelWideLinesUseParallelogram) &&
+    visitor->Visit("nonStrictWideLinesUseParallelogram", &properties->nonStrictWideLinesUseParallelogram) &&
+    visitor->Visit("blockTexelViewCompatibleMultipleLayers", &properties->blockTexelViewCompatibleMultipleLayers) &&
+    visitor->Visit("maxCombinedImageSamplerDescriptorCount", &properties->maxCombinedImageSamplerDescriptorCount) &&
+    visitor->Visit("fragmentShadingRateClampCombinerInputs", &properties->fragmentShadingRateClampCombinerInputs) &&
+    visitor->Visit("defaultRobustnessStorageBuffers", &properties->defaultRobustnessStorageBuffers) &&
+    visitor->Visit("defaultRobustnessUniformBuffers", &properties->defaultRobustnessUniformBuffers) &&
+    visitor->Visit("defaultRobustnessVertexInputs", &properties->defaultRobustnessVertexInputs) &&
+    visitor->Visit("defaultRobustnessImages", &properties->defaultRobustnessImages) &&
+    visitor->Visit("copySrcLayoutCount", &properties->copySrcLayoutCount) &&
+    visitor->VisitArray("pCopySrcLayouts", properties->copySrcLayoutCount, &properties->pCopySrcLayouts) &&
+    visitor->Visit("copyDstLayoutCount", &properties->copyDstLayoutCount) &&
+    visitor->VisitArray("pCopyDstLayouts", properties->copyDstLayoutCount, &properties->pCopyDstLayouts) &&
+    visitor->Visit("optimalTilingLayoutUUID", &properties->optimalTilingLayoutUUID) &&
+    visitor->Visit("identicalMemoryTypeRequirements", &properties->identicalMemoryTypeRequirements);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan14Features* features) {
+  return
+    visitor->Visit("globalPriorityQuery", &features->globalPriorityQuery) &&
+    visitor->Visit("shaderSubgroupRotate", &features->shaderSubgroupRotate) &&
+    visitor->Visit("shaderSubgroupRotateClustered", &features->shaderSubgroupRotateClustered) &&
+    visitor->Visit("shaderFloatControls2", &features->shaderFloatControls2) &&
+    visitor->Visit("shaderExpectAssume", &features->shaderExpectAssume) &&
+    visitor->Visit("rectangularLines", &features->rectangularLines) &&
+    visitor->Visit("bresenhamLines", &features->bresenhamLines) &&
+    visitor->Visit("smoothLines", &features->smoothLines) &&
+    visitor->Visit("stippledRectangularLines", &features->stippledRectangularLines) &&
+    visitor->Visit("stippledBresenhamLines", &features->stippledBresenhamLines) &&
+    visitor->Visit("stippledSmoothLines", &features->stippledSmoothLines) &&
+    visitor->Visit("vertexAttributeInstanceRateDivisor", &features->vertexAttributeInstanceRateDivisor) &&
+    visitor->Visit("vertexAttributeInstanceRateZeroDivisor", &features->vertexAttributeInstanceRateZeroDivisor) &&
+    visitor->Visit("indexTypeUint8", &features->indexTypeUint8) &&
+    visitor->Visit("dynamicRenderingLocalRead", &features->dynamicRenderingLocalRead) &&
+    visitor->Visit("maintenance5", &features->maintenance5) &&
+    visitor->Visit("maintenance6", &features->maintenance6) &&
+    visitor->Visit("pipelineProtectedAccess", &features->pipelineProtectedAccess) &&
+    visitor->Visit("pipelineRobustness", &features->pipelineRobustness) &&
+    visitor->Visit("hostImageCopy", &features->hostImageCopy);
+}
 // clang-format on
 
 template <typename Visitor>
@@ -1051,7 +1185,7 @@
   switch (device->properties.apiVersion ^
           VK_API_VERSION_PATCH(device->properties.apiVersion)) {
     case VK_API_VERSION_1_4:
-      // TODO: real 1.4 support here
+      ret &= visitor->Visit("core14", &device->core14);
       FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_3:
       ret &= visitor->Visit("core13", &device->core13);
@@ -1224,6 +1358,12 @@
     return true;
   }
 
+  template <typename T>
+  bool VisitArray(const char* key, uint32_t count, const T *value) {
+    object_[key] = ArrayToJsonValue(count, *value);
+    return true;
+  }
+
   Json::Value get_object() const { return object_; }
 
  private:
@@ -1288,6 +1428,15 @@
   return true;
 }
 
+inline bool AsValue(Json::Value* json_value, VkImageLayout* t) {
+  uint32_t value = 0;
+  if (!AsValue(json_value, &value))
+    return false;
+  if (!EnumTraits<VkImageLayout>::exist(value)) return false;
+  *t = static_cast<VkImageLayout>(value);
+  return true;
+}
+
 template <typename T>
 inline bool AsArray(Json::Value* json_value, uint32_t count, T* values) {
   if (json_value->type() != Json::arrayValue || json_value->size() != count)
@@ -1398,6 +1547,20 @@
     return false;
   }
 
+  template <typename T>
+  bool VisitArray(const char* key, uint32_t count, T *value) {
+    Json::Value json_value = (*object_)[key];
+    if (!json_value) {
+      if (errors_)
+        *errors_ = std::string(key) + " missing.";
+      return false;
+    }
+    if (AsArray(&json_value, count, *value)) return true;
+    if (errors_)
+      *errors_ = std::string("Wrong type for ") + std::string(key) + ".";
+    return false;
+  }
+
 
  private:
   Json::Value* object_;
diff --git a/vulkan/vkjson/vkjson.h b/vulkan/vkjson/vkjson.h
index 88f6e7d..28de680 100644
--- a/vulkan/vkjson/vkjson.h
+++ b/vulkan/vkjson/vkjson.h
@@ -84,6 +84,11 @@
   VkPhysicalDeviceVulkan13Features features;
 };
 
+struct VkJsonCore14 {
+  VkPhysicalDeviceVulkan14Properties properties;
+  VkPhysicalDeviceVulkan14Features features;
+};
+
 struct VkJsonDevice {
   VkJsonDevice() {
     memset(&properties, 0, sizeof(VkPhysicalDeviceProperties));
@@ -110,6 +115,7 @@
            sizeof(VkPhysicalDeviceShaderDrawParameterFeatures));
     memset(&core12, 0, sizeof(VkJsonCore12));
     memset(&core13, 0, sizeof(VkJsonCore13));
+    memset(&core14, 0, sizeof(VkJsonCore14));
   }
   VkPhysicalDeviceProperties properties;
   VkPhysicalDeviceFeatures features;
@@ -139,6 +145,7 @@
       external_semaphore_properties;
   VkJsonCore12 core12;
   VkJsonCore13 core13;
+  VkJsonCore14 core14;
 };
 
 struct VkJsonDeviceGroup {
diff --git a/vulkan/vkjson/vkjson_instance.cc b/vulkan/vkjson/vkjson_instance.cc
index 0ffe7e0..32bc50b 100644
--- a/vulkan/vkjson/vkjson_instance.cc
+++ b/vulkan/vkjson/vkjson_instance.cc
@@ -292,6 +292,22 @@
     vkGetPhysicalDeviceFeatures2(physical_device, &features);
   }
 
+  if (device.properties.apiVersion >= VK_API_VERSION_1_4) {
+    device.core14.properties.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_PROPERTIES;
+    device.core14.properties.pNext = properties.pNext;
+    properties.pNext = &device.core14.properties;
+
+    vkGetPhysicalDeviceProperties2(physical_device, &properties);
+
+    device.core14.features.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES;
+    device.core14.features.pNext = features.pNext;
+    features.pNext = &device.core14.features;
+
+    vkGetPhysicalDeviceFeatures2(physical_device, &features);
+  }
+
   return device;
 }