Merge changes I374aa204,I0df872b9 into main

* changes:
  libgui: Add unlimited slot support to Surfaces and Consumers
  libgui: Add support for unlimited slot BufferQueues
diff --git a/METADATA b/METADATA
index d97975c..86892cd 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,3 @@
-third_party {
+ third_party {
   license_type: NOTICE
 }
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/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/include/android/performance_hint.h b/include/android/performance_hint.h
index 2b4a5f5..9ea6549 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -478,8 +478,13 @@
 /**
  * Return the APerformanceHintSession wrapped by a Java PerformanceHintManager.Session object.
  *
- * The Java session maintains ownership over the wrapped native session, so it cannot be
- * closed using {@link APerformanceHint_closeSession}.
+ * The Java session maintains ownership over the wrapped native session, so it cannot be closed
+ * using {@link APerformanceHint_closeSession}. The return value is valid until the Java object
+ * containing this value dies.
+ *
+ * The returned pointer is intended to be used by JNI calls to access native performance APIs using
+ * a Java hint session wrapper, and then immediately discarded. Using the pointer after the death of
+ * the Java container results in undefined behavior.
  *
  * @param env The Java environment where the PerformanceHintManager.Session lives.
  * @param sessionObj The Java Session to unwrap.
diff --git a/include/android/system_health.h b/include/android/system_health.h
index 69620df..352eb72 100644
--- a/include/android/system_health.h
+++ b/include/android/system_health.h
@@ -81,10 +81,10 @@
  *
  * @return A new instance of ACpuHeadroomParams.
  */
-ACpuHeadroomParams *_Nonnull ACpuHeadroomParams_create()
+ACpuHeadroomParams* _Nonnull ACpuHeadroomParams_create(void)
 __INTRODUCED_IN(36);
 
-enum ACpuHeadroomCalculationType {
+typedef enum ACpuHeadroomCalculationType : int32_t {
     /**
      * Use the minimum headroom value within the calculation window.
      * Introduced in API level 36.
@@ -95,10 +95,9 @@
      * Introduced in API level 36.
      */
     ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1,
-};
-typedef enum ACpuHeadroomCalculationType ACpuHeadroomCalculationType;
+} ACpuHeadroomCalculationType;
 
-enum AGpuHeadroomCalculationType {
+typedef enum AGpuHeadroomCalculationType : int32_t {
     /**
      * Use the minimum headroom value within the calculation window.
      * Introduced in API level 36.
@@ -109,8 +108,7 @@
      * Introduced in API level 36.
      */
     AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1,
-};
-typedef enum AGpuHeadroomCalculationType AGpuHeadroomCalculationType;
+} AGpuHeadroomCalculationType;
 
 /**
  * Sets the headroom calculation window size in ACpuHeadroomParams.
@@ -124,7 +122,7 @@
  *                     {@link #ACpuHeadroomParams_getCalculationWindowMillis} if not set. The device
  *                     will try to use the closest feasible window size to this param.
  */
-void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams *_Nonnull params,
+void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params,
                                                    int windowMillis)
 __INTRODUCED_IN(36);
 
@@ -136,7 +134,7 @@
  * @param params The params to be set.
  * @return This will return the default value chosen by the device if the params is not set.
  */
-int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams *_Nonnull params)
+int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -151,7 +149,7 @@
  *                     {@link #AGpuHeadroomParams_getCalculationWindowMillis} if not set. The device
  *                     will try to use the closest feasible window size to this param.
  */
-void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams *_Nonnull params,
+void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params,
                                                    int windowMillis)
 __INTRODUCED_IN(36);
 
@@ -163,7 +161,7 @@
  * @param params The params to be set.
  * @return This will return the default value chosen by the device if the params is not set.
  */
-int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams *_Nonnull params)
+int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -174,7 +172,7 @@
  * @param params The params to be set.
  * @param calculationType The headroom calculation type.
  */
-void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams *_Nonnull params,
+void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams* _Nonnull params,
                                            ACpuHeadroomCalculationType calculationType)
 __INTRODUCED_IN(36);
 
@@ -187,7 +185,7 @@
  * @return The headroom calculation type.
  */
 ACpuHeadroomCalculationType
-ACpuHeadroomParams_getCalculationType(ACpuHeadroomParams *_Nonnull params)
+ACpuHeadroomParams_getCalculationType(ACpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -198,7 +196,7 @@
  * @param params The params to be set.
  * @param calculationType The headroom calculation type.
  */
-void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams *_Nonnull params,
+void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams* _Nonnull params,
                                            AGpuHeadroomCalculationType calculationType)
 __INTRODUCED_IN(36);
 
@@ -211,7 +209,7 @@
  * @return The headroom calculation type.
  */
 AGpuHeadroomCalculationType
-AGpuHeadroomParams_getCalculationType(AGpuHeadroomParams *_Nonnull params)
+AGpuHeadroomParams_getCalculationType(AGpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -223,7 +221,7 @@
  * @param tids Non-null array of TIDs, maximum 5.
  * @param tidsSize The size of the tids array.
  */
-void ACpuHeadroomParams_setTids(ACpuHeadroomParams *_Nonnull params, const int *_Nonnull tids,
+void ACpuHeadroomParams_setTids(ACpuHeadroomParams* _Nonnull params, const int* _Nonnull tids,
                                 int tidsSize)
 __INTRODUCED_IN(36);
 
@@ -238,7 +236,7 @@
  *
  * @return A new instance of AGpuHeadroomParams.
  */
-AGpuHeadroomParams *_Nonnull AGpuHeadroomParams_create()
+AGpuHeadroomParams* _Nonnull AGpuHeadroomParams_create(void)
 __INTRODUCED_IN(36);
 
 /**
@@ -248,7 +246,7 @@
  *
  * @param params The params to be deleted.
  */
-void ACpuHeadroomParams_destroy(ACpuHeadroomParams *_Nonnull params)
+void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -258,7 +256,7 @@
  *
  * @param params The params to be deleted.
  */
-void AGpuHeadroomParams_destroy(AGpuHeadroomParams *_Nonnull params)
+void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -282,8 +280,8 @@
  *         EPERM if the TIDs do not belong to the same process.
  *         ENOTSUP if API or requested params is unsupported.
  */
-int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams *_Nullable params,
-                                 float *_Nonnull outHeadroom)
+int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams* _Nullable params,
+                                 float* _Nonnull outHeadroom)
 __INTRODUCED_IN(36);
 
 /**
@@ -306,8 +304,8 @@
  *         EPIPE if failed to get the GPU headroom.
  *         ENOTSUP if API or requested params is unsupported.
  */
-int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams *_Nullable params,
-                                 float *_Nonnull outHeadroom)
+int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams* _Nullable params,
+                                 float* _Nonnull outHeadroom)
 __INTRODUCED_IN(36);
 
 /**
@@ -323,7 +321,7 @@
  *         EPIPE if failed to get the minimum polling interval.
  *         ENOTSUP if API is unsupported.
  */
-int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t *_Nonnull outMinIntervalMillis)
+int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis)
 __INTRODUCED_IN(36);
 
 /**
@@ -339,7 +337,7 @@
  *         EPIPE if failed to get the minimum polling interval.
  *         ENOTSUP if API is unsupported.
  */
-int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t *_Nonnull outMinIntervalMillis)
+int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis)
 __INTRODUCED_IN(36);
 
 #ifdef __cplusplus
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 6698d0c..623e7b9 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -775,6 +775,7 @@
 {
     LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(),
                    getpid());
+    mProcess->checkExpectingThreadPoolStart();
     mProcess->mCurrentThreads++;
     mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
 
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index a5f416f..0b7cd81 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -156,7 +156,7 @@
 
 #ifdef BINDER_WITH_KERNEL_IPC
 static void acquire_object(const sp<ProcessState>& proc, const flat_binder_object& obj,
-                           const void* who) {
+                           const void* who, bool tagFds) {
     switch (obj.hdr.type) {
         case BINDER_TYPE_BINDER:
             if (obj.binder) {
@@ -173,7 +173,7 @@
             return;
         }
         case BINDER_TYPE_FD: {
-            if (obj.cookie != 0) { // owned
+            if (tagFds && obj.cookie != 0) { // owned
                 FdTag(obj.handle, nullptr, who);
             }
             return;
@@ -611,7 +611,7 @@
                     }
                 }
 
-                acquire_object(proc, *flat, this);
+                acquire_object(proc, *flat, this, true /*tagFds*/);
             }
         }
 #else
@@ -1797,13 +1797,22 @@
         // Need to write meta-data?
         if (nullMetaData || val.binder != 0) {
             kernelFields->mObjects[kernelFields->mObjectsSize] = mDataPos;
-            acquire_object(ProcessState::self(), val, this);
+            acquire_object(ProcessState::self(), val, this, true /*tagFds*/);
             kernelFields->mObjectsSize++;
         }
 
         return finishWrite(sizeof(flat_binder_object));
     }
 
+    if (mOwner) {
+        // continueWrite does have the logic to convert this from an
+        // owned to an unowned Parcel. However, this is pretty inefficient,
+        // and it's really strange to need to do so, so prefer to avoid
+        // these paths than try to support them.
+        ALOGE("writing objects not supported on owned Parcels");
+        return PERMISSION_DENIED;
+    }
+
     if (!enoughData) {
         const status_t err = growData(sizeof(val));
         if (err != NO_ERROR) return err;
@@ -2719,6 +2728,65 @@
     return 0;
 }
 
+static void do_nothing_release_func(const uint8_t* data, size_t dataSize,
+                                    const binder_size_t* objects, size_t objectsCount) {
+    (void)data;
+    (void)dataSize;
+    (void)objects;
+    (void)objectsCount;
+}
+static void delete_data_release_func(const uint8_t* data, size_t dataSize,
+                                     const binder_size_t* objects, size_t objectsCount) {
+    delete[] data;
+    (void)dataSize;
+    (void)objects;
+    (void)objectsCount;
+}
+
+void Parcel::makeDangerousViewOf(Parcel* p) {
+    if (p->isForRpc()) {
+        // warning: this must match the logic in rpcSetDataReference
+        auto* rf = p->maybeRpcFields();
+        LOG_ALWAYS_FATAL_IF(rf == nullptr);
+        std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>> fds;
+        if (rf->mFds) {
+            fds.reserve(rf->mFds->size());
+            for (const auto& fd : *rf->mFds) {
+                fds.push_back(binder::borrowed_fd(toRawFd(fd)));
+            }
+        }
+        status_t result =
+                rpcSetDataReference(rf->mSession, p->mData, p->mDataSize,
+                                    rf->mObjectPositions.data(), rf->mObjectPositions.size(),
+                                    std::move(fds), do_nothing_release_func);
+        LOG_ALWAYS_FATAL_IF(result != OK, "Failed: %s", statusToString(result).c_str());
+    } else {
+#ifdef BINDER_WITH_KERNEL_IPC
+        // warning: this must match the logic in ipcSetDataReference
+        auto* kf = p->maybeKernelFields();
+        LOG_ALWAYS_FATAL_IF(kf == nullptr);
+
+        // Ownership of FDs is passed to the Parcel from kernel binder. This should be refactored
+        // to move this ownership out of Parcel and into release_func. However, today, Parcel
+        // always assums it can own and close FDs today. So, for purposes of testing consistency,
+        // , create new FDs it can own.
+
+        uint8_t* newData = new uint8_t[p->mDataSize]; // deleted by delete_data_release_func
+        memcpy(newData, p->mData, p->mDataSize);
+        for (size_t i = 0; i < kf->mObjectsSize; i++) {
+            flat_binder_object* flat =
+                    reinterpret_cast<flat_binder_object*>(newData + kf->mObjects[i]);
+            if (flat->hdr.type == BINDER_TYPE_FD) {
+                flat->handle = fcntl(flat->handle, F_DUPFD_CLOEXEC, 0);
+            }
+        }
+
+        ipcSetDataReference(newData, p->mDataSize, kf->mObjects, kf->mObjectsSize,
+                            delete_data_release_func);
+#endif // BINDER_WITH_KERNEL_IPC
+    }
+}
+
 void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
                                  size_t objectsCount, release_func relFunc) {
     // this code uses 'mOwner == nullptr' to understand whether it owns memory
@@ -2729,6 +2797,7 @@
     auto* kernelFields = maybeKernelFields();
     LOG_ALWAYS_FATAL_IF(kernelFields == nullptr); // guaranteed by freeData.
 
+    // must match makeDangerousViewOf
     mData = const_cast<uint8_t*>(data);
     mDataSize = mDataCapacity = dataSize;
     kernelFields->mObjects = const_cast<binder_size_t*>(objects);
@@ -2807,6 +2876,7 @@
     auto* rpcFields = maybeRpcFields();
     LOG_ALWAYS_FATAL_IF(rpcFields == nullptr); // guaranteed by markForRpc.
 
+    // must match makeDangerousViewOf
     mData = const_cast<uint8_t*>(data);
     mDataSize = mDataCapacity = dataSize;
     mOwner = relFunc;
@@ -2874,15 +2944,17 @@
 #endif // BINDER_WITH_KERNEL_IPC
 }
 
-void Parcel::acquireObjects()
-{
+void Parcel::reacquireObjects(size_t objectsSize) {
     auto* kernelFields = maybeKernelFields();
     if (kernelFields == nullptr) {
         return;
     }
 
 #ifdef BINDER_WITH_KERNEL_IPC
-    size_t i = kernelFields->mObjectsSize;
+    LOG_ALWAYS_FATAL_IF(objectsSize > kernelFields->mObjectsSize,
+                        "Object size %zu out of range of %zu", objectsSize,
+                        kernelFields->mObjectsSize);
+    size_t i = objectsSize;
     if (i == 0) {
         return;
     }
@@ -2892,8 +2964,10 @@
     while (i > 0) {
         i--;
         const flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(data + objects[i]);
-        acquire_object(proc, *flat, this);
+        acquire_object(proc, *flat, this, false /*tagFds*/); // they are already tagged
     }
+#else
+    (void) objectsSize;
 #endif // BINDER_WITH_KERNEL_IPC
 }
 
@@ -3110,12 +3184,8 @@
                 return NO_MEMORY;
             }
 
-            // Little hack to only acquire references on objects
-            // we will be keeping.
-            size_t oldObjectsSize = kernelFields->mObjectsSize;
-            kernelFields->mObjectsSize = objectsSize;
-            acquireObjects();
-            kernelFields->mObjectsSize = oldObjectsSize;
+            // only acquire references on objects we are keeping
+            reacquireObjects(objectsSize);
         }
         if (rpcFields) {
             if (status_t status = truncateRpcObjects(objectsSize); status != OK) {
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 5e7f151..0e1e9b4 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -501,6 +501,21 @@
     return mThreadPoolStarted;
 }
 
+void ProcessState::checkExpectingThreadPoolStart() const {
+    if (mThreadPoolStarted) return;
+
+    // this is also racey, but you should setup the threadpool in the main thread. If that is an
+    // issue, we can check if we are the process leader, but haven't seen the issue in practice.
+    size_t requestedThreads = mMaxThreads.load();
+
+    // if it's manually set to the default, we do ignore it here...
+    if (requestedThreads == DEFAULT_MAX_BINDER_THREADS) return;
+    if (requestedThreads == 0) return;
+
+    ALOGW("Thread pool configuration of size %zu requested, but startThreadPool was not called.",
+          requestedThreads);
+}
+
 #define DRIVER_FEATURES_PATH "/dev/binderfs/features/"
 bool ProcessState::isDriverFeatureEnabled(const DriverFeature feature) {
     // Use static variable to cache the results.
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 5924a2d..cdee17c 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -219,7 +219,6 @@
 constexpr const char* const kManualInterfaces[] = {
         "android.app.IActivityManager",
         "android.app.IUidObserver",
-        "android.drm.IDrm",
         "android.gfx.tests.ICallback",
         "android.gfx.tests.IIPCTest",
         "android.gfx.tests.ISafeInterfaceTest",
@@ -233,17 +232,13 @@
         "android.hardware.ICameraClient",
         "android.hardware.ICameraRecordingProxy",
         "android.hardware.ICameraRecordingProxyListener",
-        "android.hardware.ICrypto",
         "android.hardware.IOMXObserver",
         "android.hardware.IStreamListener",
         "android.hardware.IStreamSource",
         "android.media.IAudioService",
         "android.media.IDataSource",
-        "android.media.IDrmClient",
         "android.media.IMediaCodecList",
-        "android.media.IMediaDrmService",
         "android.media.IMediaExtractor",
-        "android.media.IMediaExtractorService",
         "android.media.IMediaHTTPConnection",
         "android.media.IMediaHTTPService",
         "android.media.IMediaLogService",
@@ -258,9 +253,6 @@
         "android.media.IMediaSource",
         "android.media.IRemoteDisplay",
         "android.media.IRemoteDisplayClient",
-        "android.media.IResourceManagerClient",
-        "android.media.IResourceManagerService",
-        "android.os.IComplexTypeInterface",
         "android.os.IPermissionController",
         "android.os.IProcessInfoService",
         "android.os.ISchedulingPolicyService",
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 0c7366e..b4efa0a 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -649,6 +649,11 @@
 
     LIBBINDER_EXPORTED void print(std::ostream& to, uint32_t flags = 0) const;
 
+    // This API is to quickly become a view of another Parcel, so that we can also
+    // test 'owner' paths quickly. It's extremely dangerous to use this API in
+    // practice, and you should never ever do it.
+    LIBBINDER_EXPORTED void makeDangerousViewOf(Parcel* p);
+
 private:
     // Close all file descriptors in the parcel at object positions >= newObjectsSize.
     void closeFileDescriptors(size_t newObjectsSize);
@@ -664,7 +669,7 @@
     void ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
                              size_t objectsCount, release_func relFunc);
     // Takes ownership even when an error is returned.
-    status_t rpcSetDataReference(
+    [[nodiscard]] status_t rpcSetDataReference(
             const sp<RpcSession>& session, const uint8_t* data, size_t dataSize,
             const uint32_t* objectTable, size_t objectTableSize,
             std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds,
@@ -672,7 +677,7 @@
 
     status_t            finishWrite(size_t len);
     void                releaseObjects();
-    void                acquireObjects();
+    void reacquireObjects(size_t objectSize);
     status_t            growData(size_t len);
     // Clear the Parcel and set the capacity to `desired`.
     // Doesn't reset the RPC session association.
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index 21bfd42..ced49c1 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -141,6 +141,8 @@
 private:
     static sp<ProcessState> init(const char* defaultDriver, bool requireDefault);
 
+    void checkExpectingThreadPoolStart() const;
+
     static void onFork();
     static void parentPostFork();
     static void childPostFork();
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index a84a0c6..64b1be2 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -23,11 +23,8 @@
 
 #ifndef __TRUSTY__
 #include <cutils/sockets.h>
-#endif
-
-#ifdef __linux__
-#include <linux/vm_sockets.h>
-#endif // __linux__
+#include "vm_sockets.h"
+#endif  // !__TRUSTY__
 
 using android::OK;
 using android::RpcServer;
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 1b24b0a..e08a763 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -104,7 +104,7 @@
 mod service;
 #[cfg(not(any(trusty, android_ndk)))]
 mod state;
-#[cfg(not(any(android_vendor, android_ndk, android_vndk)))]
+#[cfg(not(any(android_vendor, android_ndk, android_vndk, trusty)))]
 mod system_only;
 
 use binder_ndk_sys as sys;
@@ -125,7 +125,7 @@
 pub use service::{get_interface, get_service};
 #[cfg(not(any(trusty, android_ndk)))]
 pub use state::{ProcessState, ThreadState};
-#[cfg(not(any(android_vendor, android_vndk, android_ndk)))]
+#[cfg(not(any(android_vendor, android_vndk, android_ndk, trusty)))]
 pub use system_only::{delegate_accessor, Accessor, AccessorProvider, ConnectionInfo};
 
 /// Binder result containing a [`Status`] on error.
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 08fa03c..c038c95 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -344,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;
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
index 2812da7..11fcb06 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
@@ -28,6 +28,9 @@
     std::function<void(Parcel* p, FuzzedDataProvider& provider)> writeHeader;
     std::vector<sp<IBinder>> extraBinders;
     std::vector<binder::unique_fd> extraFds;
+
+    // internal state owned by fillRandomParcel, for Parcel views
+    std::vector<std::unique_ptr<Parcel>> extraParcels;
 };
 
 /**
diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp
index 192f9d5..d06b2d9 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -70,7 +70,7 @@
     uint32_t code = provider.ConsumeIntegral<uint32_t>();
     uint32_t flag = provider.ConsumeIntegral<uint32_t>();
 
-    FUZZ_LOG() << "backend: " << backend;
+    FUZZ_LOG() << "doTransactFuzz backend: " << backend;
 
     RandomParcelOptions options;
 
@@ -101,7 +101,7 @@
     // since we are only using a byte to index
     CHECK_LE(reads.size(), 255u) << reads.size();
 
-    FUZZ_LOG() << "backend: " << backend;
+    FUZZ_LOG() << "doReadFuzz backend: " << backend;
     FUZZ_LOG() << "input: " << HexString(p.data(), p.dataSize());
     FUZZ_LOG() << "instructions: " << HexString(instructions.data(), instructions.size());
 
@@ -122,10 +122,15 @@
     RandomParcelOptions options;
     P p;
 
+    // small amount of initial Parcel data, since fillRandomParcel uses makeDangerousViewOf
+    std::vector<uint8_t> parcelData =
+            provider.ConsumeBytes<uint8_t>(provider.ConsumeIntegralInRange<size_t>(0, 20));
+    fillRandomParcel(&p, FuzzedDataProvider(parcelData.data(), parcelData.size()), &options);
+
     // since we are only using a byte to index
     CHECK_LE(reads.size() + writes.size(), 255u) << reads.size();
 
-    FUZZ_LOG() << "backend: " << backend;
+    FUZZ_LOG() << "doReadWriteFuzz backend: " << backend;
 
     while (provider.remaining_bytes() > 0) {
         uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, reads.size() + writes.size() - 1);
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
index 7c19614..dfd178a 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
@@ -17,6 +17,7 @@
 #include <fuzzbinder/random_parcel.h>
 
 #include <android-base/logging.h>
+#include <binder/Functional.h>
 #include <binder/RpcSession.h>
 #include <binder/RpcTransportRaw.h>
 #include <fuzzbinder/random_binder.h>
@@ -32,10 +33,29 @@
     CHECK(OK == p->write(data.data(), data.size()));
 }
 
-void fillRandomParcel(Parcel* p, FuzzedDataProvider&& provider, RandomParcelOptions* options) {
+void fillRandomParcel(Parcel* outputParcel, FuzzedDataProvider&& provider,
+                      RandomParcelOptions* options) {
     CHECK_NE(options, nullptr);
 
-    if (provider.ConsumeBool()) {
+    const uint8_t fuzzerParcelOptions = provider.ConsumeIntegral<uint8_t>();
+    const bool resultShouldBeView = fuzzerParcelOptions & 1;
+    const bool resultShouldBeRpc = fuzzerParcelOptions & 2;
+
+    Parcel* p;
+    if (resultShouldBeView) {
+        options->extraParcels.push_back(std::make_unique<Parcel>());
+        // held for duration of test, so that view will be valid
+        p = options->extraParcels[options->extraParcels.size() - 1].get();
+    } else {
+        p = outputParcel; // directly fill out the output Parcel
+    }
+    auto viewify_guard = binder::impl::make_scope_guard([&]() {
+        if (resultShouldBeView) {
+            outputParcel->makeDangerousViewOf(p);
+        }
+    });
+
+    if (resultShouldBeRpc) {
         auto session = RpcSession::make(RpcTransportCtxFactoryRaw::make());
         CHECK_EQ(OK, session->addNullDebuggingClient());
         // Set the protocol version so that we don't crash if the session
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
index 0ed8a55..3cb6289 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
@@ -281,9 +281,9 @@
     // This buffer holds the bytes which will be used for fillRandomParcel API
     std::vector<uint8_t> fillParcelBuffer;
 
-    // Don't take rpc path
-    uint8_t rpcBranch = 0;
-    impl::writeReversedBuffer(fillParcelBuffer, rpcBranch);
+    // Use all default options.
+    uint8_t parcelOptions = 0;
+    impl::writeReversedBuffer(fillParcelBuffer, parcelOptions);
 
     // Implicit branch on this path -> options->writeHeader(p, provider)
     uint8_t writeHeaderInternal = 0;
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index 5e38ad0..dd9d4d1 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -64,14 +64,24 @@
 MODULE_EXPORT_INCLUDES += \
 	$(LIBBINDER_DIR)/ndk/include_cpp \
 
+ifeq (false,$(call TOBOOL,$(USE_SYSTEM_BINDER)))
+BINDER_EXTRA_COMPILE_FLAGS := \
+	-D__ANDROID_VENDOR__ \
+	-D__ANDROID_VNDK__ \
+
+else
+BINDER_EXTRA_COMPILE_FLAGS := \
+	-DANDROID_PLATFORM \
+
+endif
+
 MODULE_EXPORT_COMPILEFLAGS += \
 	-DBINDER_RPC_SINGLE_THREADED \
 	-DBINDER_ENABLE_LIBLOG_ASSERT \
 	-DBINDER_DISABLE_NATIVE_HANDLE \
 	-DBINDER_DISABLE_BLOB \
 	-DBINDER_NO_LIBBASE \
-	-D__ANDROID_VENDOR__ \
-	-D__ANDROID_VNDK__ \
+	$(BINDER_EXTRA_COMPILE_FLAGS)
 
 # libbinder has some deprecated declarations that we want to produce warnings
 # not errors
diff --git a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
index 2aaa061..56d711e 100644
--- a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
+++ b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
@@ -30,9 +30,14 @@
 	trusty/user/base/lib/trusty-sys \
 
 MODULE_RUSTFLAGS += \
-	--cfg 'android_vendor' \
 	--cfg 'trusty' \
 
+ifeq (false,$(call TOBOOL,$(USE_SYSTEM_BINDER)))
+MODULE_RUSTFLAGS += \
+	--cfg 'android_vendor' \
+
+endif
+
 MODULE_BINDGEN_SRC_HEADER := $(LIBBINDER_DIR)/rust/sys/BinderBindings.hpp
 
 # Add the flags from the flag file
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
index e622b22..acd74d2 100644
--- a/libs/binder/trusty/rust/rules.mk
+++ b/libs/binder/trusty/rust/rules.mk
@@ -32,9 +32,15 @@
 	trusty/user/base/lib/trusty-sys \
 
 MODULE_RUSTFLAGS += \
-	--cfg 'android_vendor' \
 	--cfg 'trusty' \
 
+ifeq (false,$(call TOBOOL,$(USE_SYSTEM_BINDER)))
+MODULE_RUSTFLAGS += \
+	--cfg 'android_vendor' \
+
+endif
+
+
 # Trusty does not have `ProcessState`, so there are a few
 # doc links in `IBinder` that are still broken.
 MODULE_RUSTFLAGS += \
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/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index be88b11..cabde22 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -3293,10 +3293,17 @@
     return statusTFromBinderStatus(status);
 }
 
-status_t SurfaceComposerClient::setActivePictureListener(
+status_t SurfaceComposerClient::addActivePictureListener(
         const sp<gui::IActivePictureListener>& listener) {
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->setActivePictureListener(listener);
+            ComposerServiceAIDL::getComposerService()->addActivePictureListener(listener);
+    return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::removeActivePictureListener(
+        const sp<gui::IActivePictureListener>& listener) {
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->removeActivePictureListener(listener);
     return statusTFromBinderStatus(status);
 }
 
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/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index 8c19bbb..da47ee2 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -607,8 +607,14 @@
     oneway void removeJankListener(int layerId, IJankListener listener, long afterVsync);
 
     /**
-     * Sets the listener used to monitor visible content that is being processed with picture
+     * Adds a listener used to monitor visible content that is being processed with picture
      * profiles.
      */
-    oneway void setActivePictureListener(IActivePictureListener listener);
+    oneway void addActivePictureListener(IActivePictureListener listener);
+
+    /**
+     * Removes a listener used to monitor visible content that is being processed with picture
+     * profiles.
+     */
+    oneway void removeActivePictureListener(IActivePictureListener listener);
 }
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/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 1c31e46..64f191b 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -425,7 +425,7 @@
     PictureProfileHandle pictureProfileHandle{PictureProfileHandle::NONE};
 
     // A value indicating the significance of the layer's content to the app's desired user
-    // experience. A lower priority will result in more likelihood of getting access to limited
+    // experience. A higher value will result in more likelihood of getting access to limited
     // resources, such as picture processing hardware.
     int32_t appContentPriority = 0;
 
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 0ce0c0a..0f66c8b 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -298,7 +298,9 @@
     static status_t removeHdrLayerInfoListener(const sp<IBinder>& displayToken,
                                                const sp<gui::IHdrLayerInfoListener>& listener);
 
-    static status_t setActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+    static status_t addActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+
+    static status_t removeActivePictureListener(const sp<gui::IActivePictureListener>& listener);
 
     /*
      * Sends a power boost to the composer. This function is asynchronous.
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index e771d6d..646e30e 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -1019,7 +1019,11 @@
         return binder::Status::ok();
     }
 
-    binder::Status setActivePictureListener(const sp<gui::IActivePictureListener>&) {
+    binder::Status addActivePictureListener(const sp<gui::IActivePictureListener>&) {
+        return binder::Status::ok();
+    }
+
+    binder::Status removeActivePictureListener(const sp<gui::IActivePictureListener>&) {
         return binder::Status::ok();
     }
 
diff --git a/libs/gui/tests/benchmarks/Android.bp b/libs/gui/tests/benchmarks/Android.bp
new file mode 100644
index 0000000..a728bef
--- /dev/null
+++ b/libs/gui/tests/benchmarks/Android.bp
@@ -0,0 +1,27 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_benchmark {
+    name: "libgui_benchmarks",
+    srcs: [
+        "*.cpp",
+    ],
+    defaults: ["libgui-defaults"],
+    static_libs: [
+        "libgmock",
+        "libgtest",
+    ],
+    shared_libs: [
+        "libgui",
+    ],
+    header_libs: [
+        "libsurfaceflinger_mocks_headers",
+        "surfaceflinger_tests_common_headers",
+    ],
+}
diff --git a/libs/gui/tests/benchmarks/Transaction_benchmarks.cpp b/libs/gui/tests/benchmarks/Transaction_benchmarks.cpp
new file mode 100644
index 0000000..0a51895
--- /dev/null
+++ b/libs/gui/tests/benchmarks/Transaction_benchmarks.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <benchmark/benchmark.h>
+#include <cstddef>
+#include <optional>
+#include <vector>
+#include "binder/Parcel.h"
+#include "gui/SurfaceComposerClient.h"
+#include "gui/SurfaceControl.h"
+#include "log/log_main.h"
+
+namespace android {
+namespace {
+using android::hardware::graphics::common::V1_1::BufferUsage;
+
+std::vector<sp<SurfaceControl>> createSurfaceControl(const char* name, size_t num) {
+    sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+    LOG_FATAL_IF(client->initCheck() != OK, "Could not init SurfaceComposerClient");
+    std::vector<sp<SurfaceControl>> surfaceControls;
+    for (size_t i = 0; i < num; i++) {
+        surfaceControls.push_back(
+                client->createSurface(String8(name), 0, 0, PIXEL_FORMAT_RGBA_8888,
+                                      ISurfaceComposerClient::eFXSurfaceBufferState));
+    }
+    return surfaceControls;
+}
+
+void applyTransaction(benchmark::State& state) {
+    std::vector<sp<SurfaceControl>> surfaceControls = createSurfaceControl(__func__, 5 /* num */);
+    for (auto _ : state) {
+        SurfaceComposerClient::Transaction t;
+        for (auto& sc : surfaceControls) {
+            t.setCrop(sc, FloatRect{1, 2, 3, 4});
+            t.setAutoRefresh(sc, true);
+            t.hide(sc);
+            t.setAlpha(sc, 0.5);
+            t.setCornerRadius(sc, 0.8);
+        }
+        Parcel p;
+        t.writeToParcel(&p);
+        t.clear();
+        benchmark::DoNotOptimize(t);
+    }
+}
+BENCHMARK(applyTransaction);
+
+// Mimic a buffer transaction with callbacks
+void applyBufferTransaction(benchmark::State& state) {
+    std::vector<sp<SurfaceControl>> surfaceControls = createSurfaceControl(__func__, 5 /* num */);
+    std::vector<sp<GraphicBuffer>> buffers;
+    for (size_t i = 0; i < surfaceControls.size(); i++) {
+        int64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+                BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
+        buffers.emplace_back(
+                sp<GraphicBuffer>::make(5, 5, PIXEL_FORMAT_RGBA_8888, 1, usageFlags, "test"));
+    }
+
+    for (auto _ : state) {
+        SurfaceComposerClient::Transaction t;
+        int i = 0;
+        for (auto& sc : surfaceControls) {
+            std::function<void(const ReleaseCallbackId&, const sp<Fence>& /*releaseFence*/,
+                               std::optional<uint32_t> currentMaxAcquiredBufferCount)>
+                    releaseBufferCallback;
+            t.setBuffer(sc, buffers[i], std::nullopt, std::nullopt, 5, releaseBufferCallback);
+        }
+        Parcel p;
+        // proxy for applying the transaction
+        t.writeToParcel(&p);
+        t.clear();
+        benchmark::DoNotOptimize(t);
+    }
+}
+BENCHMARK(applyBufferTransaction);
+
+void mergeTransaction(benchmark::State& state) {
+    std::vector<sp<SurfaceControl>> surfaceControls = createSurfaceControl(__func__, 5 /* num */);
+    for (auto _ : state) {
+        SurfaceComposerClient::Transaction t1;
+        for (auto& sc : surfaceControls) {
+            t1.setCrop(sc, FloatRect{1, 2, 3, 4});
+            t1.setAutoRefresh(sc, true);
+            t1.hide(sc);
+            t1.setAlpha(sc, 0.5);
+            t1.setCornerRadius(sc, 0.8);
+        }
+
+        SurfaceComposerClient::Transaction t2;
+        for (auto& sc : surfaceControls) {
+            t2.hide(sc);
+            t2.setAlpha(sc, 0.5);
+            t2.setCornerRadius(sc, 0.8);
+            t2.setBackgroundBlurRadius(sc, 5);
+        }
+        t1.merge(std::move(t2));
+        benchmark::DoNotOptimize(t1);
+    }
+}
+BENCHMARK(mergeTransaction);
+
+void readTransactionFromParcel(benchmark::State& state) {
+    std::vector<sp<SurfaceControl>> surfaceControls = createSurfaceControl(__func__, 5 /* num */);
+    SurfaceComposerClient::Transaction t;
+    for (auto& sc : surfaceControls) {
+        t.setCrop(sc, FloatRect{1, 2, 3, 4});
+        t.setAutoRefresh(sc, true);
+        t.hide(sc);
+        t.setAlpha(sc, 0.5);
+        t.setCornerRadius(sc, 0.8);
+    }
+    Parcel p;
+    t.writeToParcel(&p);
+    t.clear();
+
+    for (auto _ : state) {
+        SurfaceComposerClient::Transaction t2;
+        t2.readFromParcel(&p);
+        p.setDataPosition(0);
+        benchmark::DoNotOptimize(t2);
+    }
+}
+BENCHMARK(readTransactionFromParcel);
+
+} // namespace
+} // namespace android
diff --git a/libs/gui/tests/benchmarks/main.cpp b/libs/gui/tests/benchmarks/main.cpp
new file mode 100644
index 0000000..685c7c6
--- /dev/null
+++ b/libs/gui/tests/benchmarks/main.cpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <benchmark/benchmark.h>
+BENCHMARK_MAIN();
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index a4ae54b..389fb7f 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -262,6 +262,7 @@
 
     shared_libs: [
         "android.companion.virtualdevice.flags-aconfig-cc",
+        "libaconfig_storage_read_api_cc",
         "libbase",
         "libbinder",
         "libbinder_ndk",
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index d1c564d..0167c43 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -66,6 +66,7 @@
         },
     },
     shared_libs: [
+        "libaconfig_storage_read_api_cc",
         "libbase",
         "libbinder",
         "libcutils",
diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h
index 243532b..7e179d6 100644
--- a/libs/permission/include/binder/AppOpsManager.h
+++ b/libs/permission/include/binder/AppOpsManager.h
@@ -148,7 +148,10 @@
         OP_BLUETOOTH_ADVERTISE = 114,
         OP_RECORD_INCOMING_PHONE_AUDIO = 115,
         OP_NEARBY_WIFI_DEVICES = 116,
-        _NUM_OP = 117
+        // 116 - 154 omitted due to lack of use in native
+        OP_CONTROL_AUDIO = 154,
+        OP_CONTROL_AUDIO_PARTIAL = 155,
+        _NUM_OP = 156,
     };
 
     enum {
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 b986967..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.
@@ -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..10218bb 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
@@ -186,10 +187,16 @@
     SkBitmap bitmap;
     bitmap.allocPixels(info);
     if (!bitmap.installPixels(info, buffer.data(), info.minRowBytes())) {
-        LOG_ALWAYS_FATAL("unable to install pixels");
+        ALOGW("bitmap.installPixels failed, skip this Lut!");
+        return input;
     }
 
     sk_sp<SkImage> lutImage = SkImages::RasterFromBitmap(bitmap);
+    if (!lutImage) {
+        ALOGW("Got a nullptr from SkImages::RasterFromBitmap, skip this Lut!");
+        return input;
+    }
+
     mBuilder->child("image") = input;
     mBuilder->child("lut") =
             lutImage->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
@@ -197,9 +204,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 +231,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/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp
index 9a2d4f7..8bd0d0f 100644
--- a/libs/tracing_perfetto/Android.bp
+++ b/libs/tracing_perfetto/Android.bp
@@ -43,7 +43,6 @@
         "libbase",
         "libcutils",
         "libperfetto_c",
-        "android.os.flags-aconfig-cc-host",
     ],
 
     host_supported: true,
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
index a58bc77..f92f6df 100644
--- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
@@ -47,7 +47,6 @@
 #include <atomic>
 #include <mutex>
 
-#include <android_os.h>
 #include <android-base/properties.h>
 #include <cutils/trace.h>
 #include <inttypes.h>
@@ -228,10 +227,6 @@
 }
 
 void registerWithPerfetto(bool test) {
-  if (!android::os::perfetto_sdk_tracing()) {
-    return;
-  }
-
   static std::once_flag registration;
   std::call_once(registration, [test]() {
     struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
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/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/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 811692f..5bf6ebb 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -98,6 +98,22 @@
     return privacySensitiveDisplays;
 }
 
+vec2 calculatePositionOnDestinationViewport(const DisplayViewport& destinationViewport,
+                                            float pointerOffset,
+                                            DisplayTopologyPosition sourceBoundary) {
+    // destination is opposite of the source boundary
+    switch (sourceBoundary) {
+        case DisplayTopologyPosition::RIGHT:
+            return {0, pointerOffset}; // left edge
+        case DisplayTopologyPosition::TOP:
+            return {pointerOffset, destinationViewport.logicalBottom}; // bottom edge
+        case DisplayTopologyPosition::LEFT:
+            return {destinationViewport.logicalRight, pointerOffset}; // right edge
+        case DisplayTopologyPosition::BOTTOM:
+            return {pointerOffset, 0}; // top edge
+    }
+}
+
 } // namespace
 
 // --- PointerChoreographer ---
@@ -327,19 +343,19 @@
     // except sometimes near the corners.
     // In these cases this behaviour is not noticeable. We also do not apply unconsumed delta on
     // the destination display for the same reason.
-    DisplayPosition sourceBoundary;
+    DisplayTopologyPosition sourceBoundary;
     float cursorOffset = 0.0f;
     if (rotatedUnconsumedDelta.x > 0) {
-        sourceBoundary = DisplayPosition::RIGHT;
+        sourceBoundary = DisplayTopologyPosition::RIGHT;
         cursorOffset = rotatedCursorPosition.y;
     } else if (rotatedUnconsumedDelta.x < 0) {
-        sourceBoundary = DisplayPosition::LEFT;
+        sourceBoundary = DisplayTopologyPosition::LEFT;
         cursorOffset = rotatedCursorPosition.y;
     } else if (rotatedUnconsumedDelta.y > 0) {
-        sourceBoundary = DisplayPosition::BOTTOM;
+        sourceBoundary = DisplayTopologyPosition::BOTTOM;
         cursorOffset = rotatedCursorPosition.x;
     } else {
-        sourceBoundary = DisplayPosition::TOP;
+        sourceBoundary = DisplayTopologyPosition::TOP;
         cursorOffset = rotatedCursorPosition.x;
     }
 
@@ -369,8 +385,9 @@
     pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
     pc.setDisplayViewport(destinationViewport);
     vec2 destinationPosition =
-            calculateDestinationPosition(destinationViewport, cursorOffset - destinationOffset,
-                                         sourceBoundary);
+            calculatePositionOnDestinationViewport(destinationViewport,
+                                                   cursorOffset - destinationOffset,
+                                                   sourceBoundary);
 
     // Transform position back to un-rotated coordinate space before sending it to controller
     destinationPosition = pc.getDisplayTransform().inverse().transform(destinationPosition.x,
@@ -379,22 +396,6 @@
     pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
 }
 
-vec2 PointerChoreographer::calculateDestinationPosition(const DisplayViewport& destinationViewport,
-                                                        float pointerOffset,
-                                                        DisplayPosition sourceBoundary) {
-    // destination is opposite of the source boundary
-    switch (sourceBoundary) {
-        case DisplayPosition::RIGHT:
-            return {0, pointerOffset}; // left edge
-        case DisplayPosition::TOP:
-            return {pointerOffset, destinationViewport.logicalBottom}; // bottom edge
-        case DisplayPosition::LEFT:
-            return {destinationViewport.logicalRight, pointerOffset}; // right edge
-        case DisplayPosition::BOTTOM:
-            return {pointerOffset, 0}; // top edge
-    }
-}
-
 void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
     if (args.displayId == ui::LogicalDisplayId::INVALID) {
         return;
@@ -540,8 +541,7 @@
 }
 
 void PointerChoreographer::onControllerAddedOrRemovedLocked() {
-    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows() &&
-        !com::android::input::flags::connected_displays_cursor()) {
+    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
         return;
     }
     bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
@@ -607,11 +607,16 @@
     mNextListener.notify(args);
 }
 
-void PointerChoreographer::setDisplayTopology(
-        const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
-                displayTopology) {
+void PointerChoreographer::setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph) {
     std::scoped_lock _l(getLock());
-    mTopology = displayTopology;
+    mTopology = displayTopologyGraph;
+
+    // make primary display default mouse display, if it was not set
+    // or the existing display was removed
+    if (mDefaultMouseDisplayId == ui::LogicalDisplayId::INVALID ||
+        mTopology.graph.find(mDefaultMouseDisplayId) != mTopology.graph.end()) {
+        mDefaultMouseDisplayId = mTopology.primaryDisplayId;
+    }
 }
 
 void PointerChoreographer::dump(std::string& dump) {
@@ -985,73 +990,17 @@
     return ConstructorDelegate(std::move(ctor));
 }
 
-void PointerChoreographer::populateFakeDisplayTopologyLocked(
-        const std::vector<gui::DisplayInfo>& displayInfos) {
-    if (!com::android::input::flags::connected_displays_cursor()) {
-        return;
-    }
-
-    if (displayInfos.size() == mTopology.size()) {
-        bool displaysChanged = false;
-        for (const auto& displayInfo : displayInfos) {
-            if (mTopology.find(displayInfo.displayId) == mTopology.end()) {
-                displaysChanged = true;
-                break;
-            }
-        }
-
-        if (!displaysChanged) {
-            return;
-        }
-    }
-
-    // create a fake topology assuming following order
-    // default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ...
-    // This also adds a 100px offset on corresponding edge for better manual testing
-    //   ┌────────┐
-    //   │ next   ├─────────┐
-    // ┌─└───────┐┤ next 2  │ ...
-    // │ default │└─────────┘
-    // └─────────┘
-    mTopology.clear();
-
-    // treat default display as base, in real topology it should be the primary-display
-    ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT;
-    for (const auto& displayInfo : displayInfos) {
-        if (displayInfo.displayId == ui::LogicalDisplayId::DEFAULT) {
-            continue;
-        }
-        if (previousDisplay == ui::LogicalDisplayId::DEFAULT) {
-            mTopology[previousDisplay].push_back(
-                    {displayInfo.displayId, DisplayPosition::TOP, 100});
-            mTopology[displayInfo.displayId].push_back(
-                    {previousDisplay, DisplayPosition::BOTTOM, -100});
-        } else {
-            mTopology[previousDisplay].push_back(
-                    {displayInfo.displayId, DisplayPosition::RIGHT, 100});
-            mTopology[displayInfo.displayId].push_back(
-                    {previousDisplay, DisplayPosition::LEFT, -100});
-        }
-        previousDisplay = displayInfo.displayId;
-    }
-
-    // update default pointer display. In real topology it should be the primary-display
-    if (mTopology.find(mDefaultMouseDisplayId) == mTopology.end()) {
-        mDefaultMouseDisplayId = ui::LogicalDisplayId::DEFAULT;
-    }
-}
-
 std::optional<std::pair<const DisplayViewport*, float /*offset*/>>
 PointerChoreographer::findDestinationDisplayLocked(const ui::LogicalDisplayId sourceDisplayId,
-                                                   const DisplayPosition sourceBoundary,
+                                                   const DisplayTopologyPosition sourceBoundary,
                                                    float cursorOffset) const {
-    const auto& sourceNode = mTopology.find(sourceDisplayId);
-    if (sourceNode == mTopology.end()) {
+    const auto& sourceNode = mTopology.graph.find(sourceDisplayId);
+    if (sourceNode == mTopology.graph.end()) {
         // Topology is likely out of sync with viewport info, wait for it to be updated
         LOG(WARNING) << "Source display missing from topology " << sourceDisplayId;
         return std::nullopt;
     }
-    for (const AdjacentDisplay& adjacentDisplay : sourceNode->second) {
+    for (const DisplayTopologyAdjacentDisplay& adjacentDisplay : sourceNode->second) {
         if (adjacentDisplay.position != sourceBoundary) {
             continue;
         }
@@ -1064,8 +1013,8 @@
             continue;
         }
         // target position must be within target display boundary
-        const int32_t edgeSize =
-                sourceBoundary == DisplayPosition::TOP || sourceBoundary == DisplayPosition::BOTTOM
+        const int32_t edgeSize = sourceBoundary == DisplayTopologyPosition::TOP ||
+                        sourceBoundary == DisplayTopologyPosition::BOTTOM
                 ? (destinationViewport->logicalRight - destinationViewport->logicalLeft)
                 : (destinationViewport->logicalBottom - destinationViewport->logicalTop);
         if (cursorOffset >= adjacentDisplay.offsetPx &&
@@ -1093,7 +1042,6 @@
         mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
         mPointerChoreographer->onPrivacySensitiveDisplaysChangedLocked(mPrivacySensitiveDisplays);
     }
-    mPointerChoreographer->populateFakeDisplayTopologyLocked(windowInfosUpdate.displayInfos);
 }
 
 void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfosLocked(
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 939529f..c2f5ec0 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -22,6 +22,7 @@
 
 #include <android-base/thread_annotations.h>
 #include <gui/WindowInfosListener.h>
+#include <input/DisplayTopologyGraph.h>
 #include <type_traits>
 #include <unordered_set>
 
@@ -80,6 +81,11 @@
      */
     virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0;
 
+    /*
+     * Used by InputManager to notify changes in the DisplayTopology
+     */
+    virtual void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph) = 0;
+
     /**
      * This method may be called on any thread (usually by the input manager on a binder thread).
      */
@@ -103,6 +109,7 @@
                         ui::LogicalDisplayId displayId, DeviceId deviceId) override;
     void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override;
     void setFocusedDisplay(ui::LogicalDisplayId displayId) override;
+    void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph);
 
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyKey(const NotifyKeyArgs& args) override;
@@ -113,24 +120,6 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
-    // TODO(b/362719483) remove these when real topology is available
-    enum class DisplayPosition {
-        RIGHT,
-        TOP,
-        LEFT,
-        BOTTOM,
-        ftl_last = BOTTOM,
-    };
-
-    struct AdjacentDisplay {
-        ui::LogicalDisplayId displayId;
-        DisplayPosition position;
-        float offsetPx;
-    };
-    void setDisplayTopology(
-            const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
-                    displayTopology);
-
     void dump(std::string& dump) override;
 
 private:
@@ -174,18 +163,16 @@
     void handleUnconsumedDeltaLocked(PointerControllerInterface& pc, const vec2& unconsumedDelta)
             REQUIRES(getLock());
 
-    void populateFakeDisplayTopologyLocked(const std::vector<gui::DisplayInfo>& displayInfos)
+    std::optional<std::pair<const DisplayViewport*, float /*offset*/>> findDestinationDisplayLocked(
+            const ui::LogicalDisplayId sourceDisplayId,
+            const DisplayTopologyPosition sourceBoundary, float cursorOffset) const
             REQUIRES(getLock());
 
-    std::optional<std::pair<const DisplayViewport*, float /*offset*/>> findDestinationDisplayLocked(
-            const ui::LogicalDisplayId sourceDisplayId, const DisplayPosition sourceBoundary,
-            float cursorOffset) const REQUIRES(getLock());
-
-    static vec2 calculateDestinationPosition(const DisplayViewport& destinationViewport,
-                                             float pointerOffset, DisplayPosition sourceBoundary);
-
-    std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>> mTopology
-            GUARDED_BY(getLock());
+    /* Topology is initialized with default-constructed value, which is an empty topology. Till we
+     * receive setDisplayTopology call.
+     * Meanwhile Choreographer will treat every display as independent disconnected display.
+     */
+    DisplayTopologyGraph mTopology GUARDED_BY(getLock());
 
     /* This listener keeps tracks of visible privacy sensitive displays and updates the
      * choreographer if there are any changes.
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 27da3d3..1ca2998 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -2633,16 +2633,14 @@
                            ui::ROTATION_0),
     };
 
-    std::unordered_map<ui::LogicalDisplayId, std::vector<PointerChoreographer::AdjacentDisplay>>
-            mTopology{
-                    {DISPLAY_CENTER_ID,
-                     {{DISPLAY_TOP_ID, PointerChoreographer::DisplayPosition::TOP, 10.0f},
-                      {DISPLAY_RIGHT_ID, PointerChoreographer::DisplayPosition::RIGHT, 10.0f},
-                      {DISPLAY_BOTTOM_ID, PointerChoreographer::DisplayPosition::BOTTOM, 10.0f},
-                      {DISPLAY_LEFT_ID, PointerChoreographer::DisplayPosition::LEFT, 10.0f},
-                      {DISPLAY_TOP_RIGHT_CORNER_ID, PointerChoreographer::DisplayPosition::RIGHT,
-                       -90.0f}}},
-            };
+    DisplayTopologyGraph mTopology{DISPLAY_CENTER_ID,
+                                   {{DISPLAY_CENTER_ID,
+                                     {{DISPLAY_TOP_ID, DisplayTopologyPosition::TOP, 10.0f},
+                                      {DISPLAY_RIGHT_ID, DisplayTopologyPosition::RIGHT, 10.0f},
+                                      {DISPLAY_BOTTOM_ID, DisplayTopologyPosition::BOTTOM, 10.0f},
+                                      {DISPLAY_LEFT_ID, DisplayTopologyPosition::LEFT, 10.0f},
+                                      {DISPLAY_TOP_RIGHT_CORNER_ID, DisplayTopologyPosition::RIGHT,
+                                       -90.0f}}}}};
 
 private:
     DisplayViewport createViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
@@ -2706,6 +2704,11 @@
         testing::Values(
                 // Note: Upon viewport transition cursor will be positioned at the boundary of the
                 // destination, as we drop any unconsumed delta.
+                std::make_tuple("PrimaryDisplayIsDefault", AINPUT_SOURCE_MOUSE,
+                                ControllerType::MOUSE, ToolType::MOUSE,
+                                vec2(50, 50) /* initial x/y */, vec2(0, 0) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(50, 50) /* destination x/y */),
                 std::make_tuple("UnchangedDisplay", AINPUT_SOURCE_MOUSE, ControllerType::MOUSE,
                                 ToolType::MOUSE, vec2(50, 50) /* initial x/y */,
                                 vec2(25, 25) /* delta x/y */,
diff --git a/services/surfaceflinger/ActivePictureTracker.cpp b/services/surfaceflinger/ActivePictureTracker.cpp
new file mode 100644
index 0000000..4e6fa66
--- /dev/null
+++ b/services/surfaceflinger/ActivePictureTracker.cpp
@@ -0,0 +1,106 @@
+/*
+ * 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 "ActivePictureTracker.h"
+
+#include <algorithm>
+
+#include "Layer.h"
+#include "LayerFE.h"
+
+namespace android {
+
+using gui::ActivePicture;
+using gui::IActivePictureListener;
+
+void ActivePictureTracker::onLayerComposed(const Layer& layer, const LayerFE& layerFE,
+                                           const CompositionResult& result) {
+    if (result.wasPictureProfileCommitted) {
+        gui::ActivePicture picture;
+        picture.layerId = int32_t(layer.sequence);
+        picture.ownerUid = int32_t(layer.getOwnerUid());
+        // TODO(b/337330263): Why does LayerFE coming from SF have a null composition state?
+        if (layerFE.getCompositionState()) {
+            picture.pictureProfileId = layerFE.getCompositionState()->pictureProfileHandle.getId();
+        } else {
+            picture.pictureProfileId = result.pictureProfileHandle.getId();
+        }
+        mNewActivePictures.push_back(picture);
+    }
+}
+
+void ActivePictureTracker::updateAndNotifyListeners(const Listeners& listenersToAdd,
+                                                    const Listeners& listenersToRemove) {
+    Listeners newListeners = updateListeners(listenersToAdd, listenersToRemove);
+    if (updateAndHasChanged()) {
+        for (auto listener : mListeners) {
+            listener->onActivePicturesChanged(getActivePictures());
+        }
+    } else {
+        for (auto listener : newListeners) {
+            listener->onActivePicturesChanged(getActivePictures());
+        }
+    }
+}
+
+ActivePictureTracker::Listeners ActivePictureTracker::updateListeners(
+        const Listeners& listenersToAdd, const Listeners& listenersToRemove) {
+    Listeners newListeners;
+    for (auto listener : listenersToRemove) {
+        std::erase_if(mListeners, [listener](const sp<IActivePictureListener>& otherListener) {
+            return IInterface::asBinder(listener) == IInterface::asBinder(otherListener);
+        });
+    }
+    for (auto listener : listenersToAdd) {
+        if (std::find_if(mListeners.begin(), mListeners.end(),
+                         [listener](const sp<IActivePictureListener>& otherListener) {
+                             return IInterface::asBinder(listener) ==
+                                     IInterface::asBinder(otherListener);
+                         }) == mListeners.end()) {
+            newListeners.push_back(listener);
+        }
+    }
+    for (auto listener : newListeners) {
+        mListeners.push_back(listener);
+    }
+    return newListeners;
+}
+
+bool ActivePictureTracker::updateAndHasChanged() {
+    bool hasChanged = true;
+    if (mNewActivePictures.size() == mOldActivePictures.size()) {
+        auto compare = [](const ActivePicture& lhs, const ActivePicture& rhs) -> int {
+            if (lhs.layerId == rhs.layerId) {
+                return lhs.pictureProfileId < rhs.pictureProfileId;
+            }
+            return lhs.layerId < rhs.layerId;
+        };
+        std::sort(mNewActivePictures.begin(), mNewActivePictures.end(), compare);
+        if (std::equal(mNewActivePictures.begin(), mNewActivePictures.end(),
+                       mOldActivePictures.begin())) {
+            hasChanged = false;
+        }
+    }
+    std::swap(mOldActivePictures, mNewActivePictures);
+    mNewActivePictures.resize(0);
+    return hasChanged;
+}
+
+const std::vector<ActivePicture>& ActivePictureTracker::getActivePictures() const {
+    return mOldActivePictures;
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/ActivePictureUpdater.h b/services/surfaceflinger/ActivePictureTracker.h
similarity index 76%
rename from services/surfaceflinger/ActivePictureUpdater.h
rename to services/surfaceflinger/ActivePictureTracker.h
index 20779bb..cb319a5 100644
--- a/services/surfaceflinger/ActivePictureUpdater.h
+++ b/services/surfaceflinger/ActivePictureTracker.h
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include <android/gui/ActivePicture.h>
+#include <android/gui/IActivePictureListener.h>
 
 namespace android {
 
@@ -27,21 +28,28 @@
 struct CompositionResult;
 
 // Keeps track of active pictures - layers that are undergoing picture processing.
-class ActivePictureUpdater {
+class ActivePictureTracker {
 public:
+    typedef std::vector<sp<gui::IActivePictureListener>> Listeners;
+
     // Called for each visible layer when SurfaceFlinger finishes composing.
     void onLayerComposed(const Layer& layer, const LayerFE& layerFE,
                          const CompositionResult& result);
 
     // Update internals and return whether the set of active pictures have changed.
-    bool updateAndHasChanged();
+    void updateAndNotifyListeners(const Listeners& activePictureListenersToAdd,
+                                  const Listeners& activePictureListenersToRemove);
 
     // The current set of active pictures.
     const std::vector<gui::ActivePicture>& getActivePictures() const;
 
 private:
+    Listeners updateListeners(const Listeners& listenersToAdd, const Listeners& listenersToRemove);
+    bool updateAndHasChanged();
+
     std::vector<gui::ActivePicture> mOldActivePictures;
     std::vector<gui::ActivePicture> mNewActivePictures;
+    Listeners mListeners;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/ActivePictureUpdater.cpp b/services/surfaceflinger/ActivePictureUpdater.cpp
deleted file mode 100644
index 210e948..0000000
--- a/services/surfaceflinger/ActivePictureUpdater.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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 "ActivePictureUpdater.h"
-
-#include <algorithm>
-
-#include "Layer.h"
-#include "LayerFE.h"
-
-namespace android {
-
-void ActivePictureUpdater::onLayerComposed(const Layer& layer, const LayerFE& layerFE,
-                                           const CompositionResult& result) {
-    if (result.wasPictureProfileCommitted) {
-        gui::ActivePicture picture;
-        picture.layerId = int32_t(layer.sequence);
-        picture.ownerUid = int32_t(layer.getOwnerUid());
-        // TODO(b/337330263): Why does LayerFE coming from SF have a null composition state?
-        if (layerFE.getCompositionState()) {
-            picture.pictureProfileId = layerFE.getCompositionState()->pictureProfileHandle.getId();
-        } else {
-            picture.pictureProfileId = result.pictureProfileHandle.getId();
-        }
-        mNewActivePictures.push_back(picture);
-    }
-}
-
-bool ActivePictureUpdater::updateAndHasChanged() {
-    bool hasChanged = true;
-    if (mNewActivePictures.size() == mOldActivePictures.size()) {
-        auto compare = [](const gui::ActivePicture& lhs, const gui::ActivePicture& rhs) -> int {
-            if (lhs.layerId == rhs.layerId) {
-                return lhs.pictureProfileId < rhs.pictureProfileId;
-            }
-            return lhs.layerId < rhs.layerId;
-        };
-        std::sort(mNewActivePictures.begin(), mNewActivePictures.end(), compare);
-        if (std::equal(mNewActivePictures.begin(), mNewActivePictures.end(),
-                       mOldActivePictures.begin())) {
-            hasChanged = false;
-        }
-    }
-    std::swap(mOldActivePictures, mNewActivePictures);
-    mNewActivePictures.resize(0);
-    return hasChanged;
-}
-
-const std::vector<gui::ActivePicture>& ActivePictureUpdater::getActivePictures() const {
-    return mOldActivePictures;
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 3f3d2c6..05f7d1b 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\"",
@@ -67,6 +67,7 @@
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
+        "android.os.flags-aconfig-cc-host",
         "libbase",
         "libbinder",
         "libbinder_ndk",
@@ -79,7 +80,6 @@
         "libhidlbase",
         "liblog",
         "libnativewindow",
-        "libpowermanager",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
         "libstatslog_surfaceflinger",
@@ -199,7 +199,7 @@
     name: "libsurfaceflinger_sources",
     srcs: [
         ":libsurfaceflinger_backend_sources",
-        "ActivePictureUpdater.cpp",
+        "ActivePictureTracker.cpp",
         "BackgroundExecutor.cpp",
         "Client.cpp",
         "ClientCache.cpp",
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..734d764 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -810,7 +810,7 @@
     }
     auto compare = [](const ::android::compositionengine::OutputLayer* lhs,
                       const ::android::compositionengine::OutputLayer* rhs) {
-        return lhs->getPictureProfilePriority() > rhs->getPictureProfilePriority();
+        return lhs->getPictureProfilePriority() < rhs->getPictureProfilePriority();
     };
     std::priority_queue<::android::compositionengine::OutputLayer*,
                         std::vector<::android::compositionengine::OutputLayer*>, decltype(compare)>
@@ -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..09ad9fa 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;
@@ -5080,14 +5100,14 @@
     InjectedLayer layer1;
     injectOutputLayer(layer1);
     PictureProfileHandle profileForLayer1(1);
-    EXPECT_CALL(*layer1.outputLayer, getPictureProfilePriority()).WillRepeatedly(Return(3));
+    EXPECT_CALL(*layer1.outputLayer, getPictureProfilePriority()).WillRepeatedly(Return(1));
     EXPECT_CALL(*layer1.outputLayer, getPictureProfileHandle())
             .WillRepeatedly(ReturnRef(profileForLayer1));
 
     InjectedLayer layer2;
     injectOutputLayer(layer2);
     PictureProfileHandle profileForLayer2(2);
-    EXPECT_CALL(*layer2.outputLayer, getPictureProfilePriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer2.outputLayer, getPictureProfilePriority()).WillRepeatedly(Return(3));
     EXPECT_CALL(*layer2.outputLayer, getPictureProfileHandle())
             .WillRepeatedly(ReturnRef(profileForLayer2));
 
@@ -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);
@@ -5143,14 +5163,14 @@
     InjectedLayer layer1;
     injectOutputLayer(layer1);
     PictureProfileHandle profileForLayer1(1);
-    EXPECT_CALL(*layer1.outputLayer, getPictureProfilePriority()).WillRepeatedly(Return(3));
+    EXPECT_CALL(*layer1.outputLayer, getPictureProfilePriority()).WillRepeatedly(Return(1));
     EXPECT_CALL(*layer1.outputLayer, getPictureProfileHandle())
             .WillRepeatedly(ReturnRef(profileForLayer1));
 
     InjectedLayer layer2;
     injectOutputLayer(layer2);
     PictureProfileHandle profileForLayer2(2);
-    EXPECT_CALL(*layer2.outputLayer, getPictureProfilePriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer2.outputLayer, getPictureProfilePriority()).WillRepeatedly(Return(3));
     EXPECT_CALL(*layer2.outputLayer, getPictureProfileHandle())
             .WillRepeatedly(ReturnRef(profileForLayer2));
 
@@ -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/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 25f6513..8529c72 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -1678,6 +1678,29 @@
     return error;
 }
 
+Error AidlComposer::getLuts(Display display, const std::vector<sp<GraphicBuffer>>& buffers,
+                            std::vector<aidl::android::hardware::graphics::composer3::Luts>* luts) {
+    std::vector<aidl::android::hardware::graphics::composer3::Buffer> aidlBuffers;
+    aidlBuffers.reserve(buffers.size());
+
+    for (auto& buffer : buffers) {
+        if (buffer.get()) {
+            aidl::android::hardware::graphics::composer3::Buffer aidlBuffer;
+            aidlBuffer.handle.emplace(::android::dupToAidl(buffer->getNativeBuffer()->handle));
+            aidlBuffers.emplace_back(std::move(aidlBuffer));
+        }
+    }
+
+    const auto status =
+            mAidlComposerClient->getLuts(translate<int64_t>(display), aidlBuffers, luts);
+    if (!status.isOk()) {
+        ALOGE("getLuts failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+
+    return Error::NONE;
+}
+
 ftl::Optional<std::reference_wrapper<ComposerClientWriter>> AidlComposer::getWriter(Display display)
         REQUIRES_SHARED(mMutex) {
     return mWriters.get(display);
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 6b5ebc5..82006f4 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -245,6 +245,8 @@
     Error getMaxLayerPictureProfiles(Display, int32_t* outMaxProfiles) override;
     Error setDisplayPictureProfileId(Display, PictureProfileId id) override;
     Error setLayerPictureProfileId(Display, Layer, PictureProfileId id) override;
+    Error getLuts(Display, const std::vector<sp<GraphicBuffer>>&,
+                  std::vector<aidl::android::hardware::graphics::composer3::Luts>*) override;
 
 private:
     // Many public functions above simply write a command into the command
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index ff292fa..6e431bb 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -46,6 +46,7 @@
 #include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayLuts.h>
 #include <aidl/android/hardware/graphics/composer3/IComposerCallback.h>
+#include <aidl/android/hardware/graphics/composer3/Luts.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 #include <optional>
@@ -313,6 +314,8 @@
     virtual Error getMaxLayerPictureProfiles(Display display, int32_t* outMaxProfiles) = 0;
     virtual Error setDisplayPictureProfileId(Display display, PictureProfileId id) = 0;
     virtual Error setLayerPictureProfileId(Display display, Layer layer, PictureProfileId id) = 0;
+    virtual Error getLuts(Display display, const std::vector<sp<GraphicBuffer>>&,
+                          std::vector<V3_0::Luts>*) = 0;
 };
 
 } // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 081f4aa..99a67e4 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -669,6 +669,12 @@
     return static_cast<Error>(error);
 }
 
+Error Display::getLuts(const std::vector<sp<GraphicBuffer>>& buffers,
+                       std::vector<aidl::android::hardware::graphics::composer3::Luts>* outLuts) {
+    const auto error = mComposer.getLuts(mId, buffers, outLuts);
+    return static_cast<Error>(error);
+}
+
 // For use by Device
 
 void Display::setConnected(bool connected) {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 6740d8a..c3deb84 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -203,6 +203,9 @@
     [[nodiscard]] virtual hal::Error getMaxLayerPictureProfiles(int32_t* maxProfiles) = 0;
     [[nodiscard]] virtual hal::Error setPictureProfileHandle(
             const PictureProfileHandle& handle) = 0;
+    [[nodiscard]] virtual hal::Error getLuts(
+            const std::vector<android::sp<android::GraphicBuffer>>&,
+            std::vector<aidl::android::hardware::graphics::composer3::Luts>*) = 0;
 };
 
 namespace impl {
@@ -288,6 +291,8 @@
     hal::Error setIdleTimerEnabled(std::chrono::milliseconds timeout) override;
     hal::Error getMaxLayerPictureProfiles(int32_t* maxProfiles) override;
     hal::Error setPictureProfileHandle(const android::PictureProfileHandle& handle) override;
+    hal::Error getLuts(const std::vector<android::sp<android::GraphicBuffer>>&,
+                       std::vector<aidl::android::hardware::graphics::composer3::Luts>*) override;
 
     // Other Display methods
     hal::HWDisplayId getId() const override { return mId; }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 55ccdef..7f94428 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -1046,6 +1046,16 @@
     return NO_ERROR;
 }
 
+status_t HWComposer::getLuts(
+        PhysicalDisplayId displayId, const std::vector<sp<GraphicBuffer>>& buffers,
+        std::vector<aidl::android::hardware::graphics::composer3::Luts>* luts) {
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+    auto& hwcDisplay = mDisplayData[displayId].hwcDisplay;
+    auto error = hwcDisplay->getLuts(buffers, luts);
+    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
 const std::unordered_map<std::string, bool>& HWComposer::getSupportedLayerGenericMetadata() const {
     return mSupportedLayerGenericMetadata;
 }
@@ -1180,6 +1190,7 @@
             return DisplayIdentificationInfo{.id = PhysicalDisplayId::fromPort(port),
                                              .name = isPrimary ? "Primary display"
                                                                : "Secondary display",
+                                             .port = port,
                                              .deviceProductInfo = std::nullopt};
         }();
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 52662cf..b1b997a 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -55,6 +55,7 @@
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayLuts.h>
 #include <aidl/android/hardware/graphics/composer3/LutProperties.h>
+#include <aidl/android/hardware/graphics/composer3/Luts.h>
 #include <aidl/android/hardware/graphics/composer3/OutputType.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
@@ -324,6 +325,8 @@
     virtual int32_t getMaxLayerPictureProfiles(PhysicalDisplayId) = 0;
     virtual status_t setDisplayPictureProfileHandle(PhysicalDisplayId,
                                                     const PictureProfileHandle& handle) = 0;
+    virtual status_t getLuts(PhysicalDisplayId, const std::vector<sp<GraphicBuffer>>&,
+                             std::vector<aidl::android::hardware::graphics::composer3::Luts>*) = 0;
 };
 
 static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -491,6 +494,8 @@
     int32_t getMaxLayerPictureProfiles(PhysicalDisplayId) override;
     status_t setDisplayPictureProfileHandle(PhysicalDisplayId,
                                             const android::PictureProfileHandle& profile) override;
+    status_t getLuts(PhysicalDisplayId, const std::vector<sp<GraphicBuffer>>&,
+                     std::vector<aidl::android::hardware::graphics::composer3::Luts>*) override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 5703a2d..ec15539 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -1452,6 +1452,11 @@
     return Error::UNSUPPORTED;
 }
 
+Error HidlComposer::getLuts(Display, const std::vector<sp<GraphicBuffer>>&,
+                            std::vector<aidl::android::hardware::graphics::composer3::Luts>*) {
+    return Error::UNSUPPORTED;
+}
+
 Error HidlComposer::setDisplayPictureProfileId(Display, PictureProfileId) {
     return Error::UNSUPPORTED;
 }
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 42ba9a9..cacdb8c 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -359,6 +359,8 @@
     Error getMaxLayerPictureProfiles(Display, int32_t* outMaxProfiles) override;
     Error setDisplayPictureProfileId(Display, PictureProfileId) override;
     Error setLayerPictureProfileId(Display, Layer, PictureProfileId) override;
+    Error getLuts(Display, const std::vector<sp<GraphicBuffer>>&,
+                  std::vector<aidl::android::hardware::graphics::composer3::Luts>*) override;
 
 private:
     class CommandWriter : public CommandWriterBase {
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 58f6b96..367132c 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -418,7 +418,7 @@
     }
     if (forceUpdate || requested.what & layer_state_t::eAppContentPriorityChanged) {
         // TODO(b/337330263): Also consider the system-determined priority of the app
-        pictureProfilePriority = requested.appContentPriority;
+        pictureProfilePriority = int64_t(requested.appContentPriority) + INT_MAX;
     }
 
     if (forceUpdate || requested.what & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
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 a2cdd46..3fdddac 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -209,7 +209,6 @@
         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
@@ -471,6 +470,7 @@
     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 d7d567c..e05c5bd9 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -37,6 +37,7 @@
 #include <android/hardware/configstore/1.1/types.h>
 #include <android/native_window.h>
 #include <android/os/IInputFlinger.h>
+#include <android_os.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
@@ -126,7 +127,6 @@
 #include <gui/SchedulingPolicy.h>
 #include <gui/SyncScreenCaptureListener.h>
 #include <ui/DisplayIdentification.h>
-#include "ActivePictureUpdater.h"
 #include "BackgroundExecutor.h"
 #include "Client.h"
 #include "ClientCache.h"
@@ -742,7 +742,10 @@
     mBootFinished = true;
     FlagManager::getMutableInstance().markBootCompleted();
 
-    ::tracing_perfetto::registerWithPerfetto();
+    if (android::os::perfetto_sdk_tracing()) {
+        ::tracing_perfetto::registerWithPerfetto();
+    }
+
     mInitBootPropsFuture.wait();
     mRenderEnginePrimeCacheFuture.wait();
 
@@ -1444,14 +1447,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;
@@ -1466,8 +1469,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(),
@@ -1478,6 +1481,8 @@
     if (pendingModeOpt->emitEvent) {
         mScheduler->onDisplayModeChanged(displayId, activeMode, /*clearContentRequirements*/ true);
     }
+
+    return true;
 }
 
 void SurfaceFlinger::dropModeRequest(PhysicalDisplayId displayId) {
@@ -2659,7 +2664,10 @@
 
         for (const auto [displayId, _] : frameTargets) {
             if (mDisplayModeController.isModeSetPending(displayId)) {
-                finalizeDisplayModeChange(displayId);
+                if (!finalizeDisplayModeChange(displayId)) {
+                    mScheduler->scheduleFrame();
+                    return false;
+                }
             }
         }
     }
@@ -2788,12 +2796,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());
@@ -2810,7 +2849,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());
+                }
             }
         }
     }
@@ -2914,7 +2955,7 @@
             layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
         }
         if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
-            mActivePictureUpdater.onLayerComposed(*layer, *layerFE, compositionResult);
+            mActivePictureTracker.onLayerComposed(*layer, *layerFE, compositionResult);
         }
     }
 
@@ -3226,8 +3267,8 @@
     std::vector<std::pair<std::shared_ptr<compositionengine::Display>, sp<HdrLayerInfoReporter>>>
             hdrInfoListeners;
     bool haveNewHdrInfoListeners = false;
-    sp<gui::IActivePictureListener> activePictureListener;
-    bool haveNewActivePictureListener = false;
+    ActivePictureTracker::Listeners activePictureListenersToAdd;
+    ActivePictureTracker::Listeners activePictureListenersToRemove;
     {
         Mutex::Autolock lock(mStateLock);
         if (mFpsReporter) {
@@ -3249,9 +3290,8 @@
         haveNewHdrInfoListeners = mAddingHDRLayerInfoListener; // grab this with state lock
         mAddingHDRLayerInfoListener = false;
 
-        activePictureListener = mActivePictureListener;
-        haveNewActivePictureListener = mHaveNewActivePictureListener;
-        mHaveNewActivePictureListener = false;
+        std::swap(activePictureListenersToAdd, mActivePictureListenersToAdd);
+        std::swap(activePictureListenersToRemove, mActivePictureListenersToRemove);
     }
 
     if (haveNewHdrInfoListeners || mHdrLayerInfoChanged) {
@@ -3315,14 +3355,10 @@
     mHdrLayerInfoChanged = false;
 
     if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
-        // Track, update and notify changes to active pictures - layers that are undergoing picture
-        // processing
-        if (mActivePictureUpdater.updateAndHasChanged() || haveNewActivePictureListener) {
-            if (activePictureListener) {
-                activePictureListener->onActivePicturesChanged(
-                        mActivePictureUpdater.getActivePictures());
-            }
-        }
+        // Track, update and notify changes to active pictures - layers that are undergoing
+        // picture processing
+        mActivePictureTracker.updateAndNotifyListeners(activePictureListenersToAdd,
+                                                       activePictureListenersToRemove);
     }
 
     mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
@@ -3634,10 +3670,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.
@@ -3650,7 +3689,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));
 
@@ -4566,7 +4605,6 @@
     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
@@ -7149,8 +7187,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,
@@ -7207,7 +7244,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) {
@@ -7318,8 +7355,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
@@ -7377,7 +7413,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();
 
@@ -7392,6 +7428,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) {
@@ -7420,9 +7460,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();
 }
 
@@ -7465,10 +7547,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;
@@ -7484,74 +7567,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) {
@@ -7573,8 +7622,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) {
@@ -7642,9 +7691,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();
@@ -8175,12 +8223,20 @@
     }));
 }
 
-void SurfaceFlinger::setActivePictureListener(const sp<gui::IActivePictureListener>& listener) {
-    if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
-        Mutex::Autolock lock(mStateLock);
-        mActivePictureListener = listener;
-        mHaveNewActivePictureListener = listener != nullptr;
-    }
+void SurfaceFlinger::addActivePictureListener(const sp<gui::IActivePictureListener>& listener) {
+    Mutex::Autolock lock(mStateLock);
+    std::erase_if(mActivePictureListenersToRemove, [listener](const auto& otherListener) {
+        return IInterface::asBinder(listener) == IInterface::asBinder(otherListener);
+    });
+    mActivePictureListenersToAdd.push_back(listener);
+}
+
+void SurfaceFlinger::removeActivePictureListener(const sp<gui::IActivePictureListener>& listener) {
+    Mutex::Autolock lock(mStateLock);
+    std::erase_if(mActivePictureListenersToAdd, [listener](const auto& otherListener) {
+        return IInterface::asBinder(listener) == IInterface::asBinder(otherListener);
+    });
+    mActivePictureListenersToRemove.push_back(listener);
 }
 
 std::shared_ptr<renderengine::ExternalTexture> SurfaceFlinger::getExternalTextureFromBufferData(
@@ -9133,11 +9189,20 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::setActivePictureListener(
+binder::Status SurfaceComposerAIDL::addActivePictureListener(
         const sp<gui::IActivePictureListener>& listener) {
     status_t status = checkObservePictureProfilesPermission();
     if (status == OK) {
-        mFlinger->setActivePictureListener(listener);
+        mFlinger->addActivePictureListener(listener);
+    }
+    return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::removeActivePictureListener(
+        const sp<gui::IActivePictureListener>& listener) {
+    status_t status = checkObservePictureProfilesPermission();
+    if (status == OK) {
+        mFlinger->removeActivePictureListener(listener);
     }
     return binderStatusFromStatusT(status);
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 1e2c087..c85c084 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -69,7 +69,7 @@
 #include <ui/FenceResult.h>
 
 #include <common/FlagManager.h>
-#include "ActivePictureUpdater.h"
+#include "ActivePictureTracker.h"
 #include "BackgroundExecutor.h"
 #include "Display/DisplayModeController.h"
 #include "Display/PhysicalDisplay.h"
@@ -667,7 +667,9 @@
 
     void updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, int32_t maxLevel);
 
-    void setActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+    void addActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+
+    void removeActivePictureListener(const sp<gui::IActivePictureListener>& listener);
 
     // IBinder::DeathRecipient overrides:
     void binderDied(const wp<IBinder>& who) override;
@@ -730,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);
@@ -872,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);
@@ -880,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();
 
@@ -1402,9 +1409,9 @@
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
             GUARDED_BY(mStateLock);
 
-    sp<gui::IActivePictureListener> mActivePictureListener GUARDED_BY(mStateLock);
-    bool mHaveNewActivePictureListener GUARDED_BY(mStateLock);
-    ActivePictureUpdater mActivePictureUpdater GUARDED_BY(kMainThreadContext);
+    ActivePictureTracker mActivePictureTracker GUARDED_BY(kMainThreadContext);
+    ActivePictureTracker::Listeners mActivePictureListenersToAdd GUARDED_BY(mStateLock);
+    ActivePictureTracker::Listeners mActivePictureListenersToRemove GUARDED_BY(mStateLock);
 
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
@@ -1641,8 +1648,8 @@
     binder::Status flushJankData(int32_t layerId) override;
     binder::Status removeJankListener(int32_t layerId, const sp<gui::IJankListener>& listener,
                                       int64_t afterVsync) override;
-    binder::Status setActivePictureListener(const sp<gui::IActivePictureListener>& listener);
-    binder::Status clearActivePictureListener();
+    binder::Status addActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+    binder::Status removeActivePictureListener(const sp<gui::IActivePictureListener>& listener);
 
 private:
     static const constexpr bool kUsePermissionCache = true;
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 5e78426..e80cd78 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -112,60 +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
@@ -266,6 +269,7 @@
 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 d8887f5..c7f97b4 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -48,62 +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 bdd826d..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"
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 76%
rename from services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
rename to services/surfaceflinger/tests/MultiDisplay_test.cpp
index 65add63..54bb253 100644
--- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
+++ b/services/surfaceflinger/tests/MultiDisplay_test.cpp
@@ -33,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();
@@ -105,7 +105,7 @@
     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);
@@ -124,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.
 
@@ -150,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.
 
@@ -181,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/unittests/ActivePictureTrackerTest.cpp b/services/surfaceflinger/tests/unittests/ActivePictureTrackerTest.cpp
new file mode 100644
index 0000000..8011309
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/ActivePictureTrackerTest.cpp
@@ -0,0 +1,482 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android/gui/ActivePicture.h>
+#include <android/gui/IActivePictureListener.h>
+#include <compositionengine/mock/CompositionEngine.h>
+#include <mock/DisplayHardware/MockComposer.h>
+#include <mock/MockLayer.h>
+#include <renderengine/mock/RenderEngine.h>
+
+#include "ActivePictureTracker.h"
+#include "LayerFE.h"
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+using android::compositionengine::LayerFECompositionState;
+using android::gui::ActivePicture;
+using android::gui::IActivePictureListener;
+using android::mock::MockLayer;
+using surfaceflinger::frontend::LayerSnapshot;
+using testing::_;
+using testing::NiceMock;
+using testing::Return;
+using testing::SizeIs;
+using testing::StrictMock;
+
+class TestableLayerFE : public LayerFE {
+public:
+    TestableLayerFE() : LayerFE("TestableLayerFE"), snapshot(*(new LayerSnapshot)) {
+        mSnapshot = std::unique_ptr<LayerSnapshot>(&snapshot);
+    }
+
+    LayerSnapshot& snapshot;
+};
+
+class MockActivePictureListener : public gui::BnActivePictureListener {
+public:
+    operator ActivePictureTracker::Listeners const() {
+        return {sp<IActivePictureListener>::fromExisting(this)};
+    }
+
+    MOCK_METHOD(binder::Status, onActivePicturesChanged, (const std::vector<ActivePicture>&),
+                (override));
+};
+
+class ActivePictureTrackerTest : public testing::Test {
+protected:
+    const static ActivePictureTracker::Listeners NO_LISTENERS;
+
+    SurfaceFlinger* flinger() {
+        if (!mFlingerSetup) {
+            mFlinger.setupMockScheduler();
+            mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+            mFlinger.setupRenderEngine(std::make_unique<renderengine::mock::RenderEngine>());
+            mFlingerSetup = true;
+        }
+        return mFlinger.flinger();
+    }
+
+    sp<NiceMock<MockLayer>> createMockLayer(int layerId, int ownerUid) {
+        auto layer = sp<NiceMock<MockLayer>>::make(flinger(), layerId);
+        EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(ownerUid)));
+        return layer;
+    }
+
+    sp<StrictMock<MockActivePictureListener>> createMockListener() {
+        return sp<StrictMock<MockActivePictureListener>>::make();
+    }
+
+    ActivePictureTracker::Listeners mListenersToAdd;
+    ActivePictureTracker::Listeners mListenersToRemove;
+
+private:
+    TestableSurfaceFlinger mFlinger;
+    bool mFlingerSetup = false;
+};
+
+const ActivePictureTracker::Listeners ActivePictureTrackerTest::NO_LISTENERS;
+
+// Hack to workaround initializer lists not working for parcelables because parcelables inherit from
+// Parcelable, which has a virtual destructor.
+auto UnorderedElementsAre(std::initializer_list<std::tuple<int32_t, int32_t, int64_t>> tuples) {
+    std::vector<ActivePicture> activePictures;
+    for (auto tuple : tuples) {
+        ActivePicture ap;
+        ap.layerId = std::get<0>(tuple);
+        ap.ownerUid = std::get<1>(tuple);
+        ap.pictureProfileId = std::get<2>(tuple);
+        activePictures.push_back(ap);
+    }
+    return testing::UnorderedElementsAreArray(activePictures);
+}
+
+// Parcelables don't define this for matchers, which is unfortunate
+void PrintTo(const ActivePicture& activePicture, std::ostream* os) {
+    *os << activePicture.toString();
+}
+
+TEST_F(ActivePictureTrackerTest, whenListenerAdded_called) {
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(0))).Times(1);
+    tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+}
+
+TEST_F(ActivePictureTrackerTest, whenListenerAdded_withListenerAlreadyAdded_notCalled) {
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(0))).Times(1);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+    {
+        EXPECT_CALL(*listener, onActivePicturesChanged(_)).Times(0);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenListenerAdded_withUncommittedProfile_calledWithNone) {
+    auto layer = createMockLayer(100, 10);
+    TestableLayerFE layerFE;
+
+    ActivePictureTracker tracker;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+        tracker.updateAndNotifyListeners(NO_LISTENERS, NO_LISTENERS);
+    }
+    {
+        auto listener = createMockListener();
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(0))).Times(1);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenListenerAdded_withCommittedProfile_calledWithActivePicture) {
+    auto layer = createMockLayer(100, 10);
+    TestableLayerFE layerFE;
+
+    ActivePictureTracker tracker;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        auto listener = createMockListener();
+        EXPECT_CALL(*listener, onActivePicturesChanged(_))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 1}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenProfileAdded_calledWithActivePicture) {
+    auto layer = createMockLayer(100, 10);
+    TestableLayerFE layerFE;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(0))).Times(1);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(_))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 1}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(NO_LISTENERS, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenContinuesUsingProfile_notCalled) {
+    auto layer = createMockLayer(100, 10);
+    TestableLayerFE layerFE;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(1))).Times(1);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(_)).Times(0);
+        tracker.updateAndNotifyListeners(NO_LISTENERS, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenProfileIsRemoved_calledWithNoActivePictures) {
+    auto layer = createMockLayer(100, 10);
+    TestableLayerFE layerFE;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(1))).Times(1);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(0))).Times(1);
+        tracker.updateAndNotifyListeners(NO_LISTENERS, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenProfileIsNotCommitted_calledWithNoActivePictures) {
+    auto layer = createMockLayer(100, 10);
+    TestableLayerFE layerFE;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(1))).Times(1);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(0))).Times(1);
+        tracker.updateAndNotifyListeners(NO_LISTENERS, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenProfileChanges_calledWithDifferentProfile) {
+    auto layer = createMockLayer(100, 10);
+    TestableLayerFE layerFE;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(1)))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 1}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(1)))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 2}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(NO_LISTENERS, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenMultipleCommittedProfiles_calledWithMultipleActivePictures) {
+    auto layer1 = createMockLayer(100, 10);
+    TestableLayerFE layerFE1;
+
+    auto layer2 = createMockLayer(200, 20);
+    TestableLayerFE layerFE2;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE2.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(2)))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 1}, {200, 20, 2}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenNonCommittedProfileChanges_notCalled) {
+    auto layer1 = createMockLayer(100, 10);
+    TestableLayerFE layerFE1;
+
+    auto layer2 = createMockLayer(200, 20);
+    TestableLayerFE layerFE2;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        tracker.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(1))).Times(1);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        tracker.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(_)).Times(0);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenDifferentLayerUsesSameProfile_called) {
+    auto layer1 = createMockLayer(100, 10);
+    TestableLayerFE layerFE1;
+
+    auto layer2 = createMockLayer(200, 20);
+    TestableLayerFE layerFE2;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE2.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(_))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 1}, {200, 20, 2}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE1.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE2.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(_))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 2}, {200, 20, 1}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(NO_LISTENERS, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenSameUidDifferentLayerUsesSameProfile_called) {
+    auto layer1 = createMockLayer(100, 10);
+    TestableLayerFE layerFE1;
+
+    auto layer2 = createMockLayer(200, 10);
+    TestableLayerFE layerFE2;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE2.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(_))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 1}, {200, 10, 2}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE1.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE2.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(_))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 2}, {200, 10, 1}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(NO_LISTENERS, NO_LISTENERS);
+    }
+}
+
+TEST_F(ActivePictureTrackerTest, whenNewLayerUsesSameProfile_called) {
+    auto layer1 = createMockLayer(100, 10);
+    TestableLayerFE layerFE1;
+
+    ActivePictureTracker tracker;
+    auto listener = createMockListener();
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(SizeIs(1))).Times(1);
+        tracker.updateAndNotifyListeners(*listener, NO_LISTENERS);
+    }
+
+    auto layer2 = createMockLayer(200, 10);
+    TestableLayerFE layerFE2;
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE2.onPictureProfileCommitted();
+        tracker.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        EXPECT_CALL(*listener, onActivePicturesChanged(_))
+                .WillOnce([](const std::vector<gui::ActivePicture>& activePictures) {
+                    EXPECT_THAT(activePictures, UnorderedElementsAre({{100, 10, 1}, {200, 10, 1}}));
+                    return binder::Status::ok();
+                });
+        tracker.updateAndNotifyListeners(NO_LISTENERS, NO_LISTENERS);
+    }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/ActivePictureUpdaterTest.cpp b/services/surfaceflinger/tests/unittests/ActivePictureUpdaterTest.cpp
deleted file mode 100644
index b926d2f..0000000
--- a/services/surfaceflinger/tests/unittests/ActivePictureUpdaterTest.cpp
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * 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 <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <android/gui/ActivePicture.h>
-#include <android/gui/IActivePictureListener.h>
-#include <compositionengine/mock/CompositionEngine.h>
-#include <mock/DisplayHardware/MockComposer.h>
-#include <mock/MockLayer.h>
-#include <renderengine/mock/RenderEngine.h>
-
-#include "ActivePictureUpdater.h"
-#include "LayerFE.h"
-#include "TestableSurfaceFlinger.h"
-
-namespace android {
-
-using android::compositionengine::LayerFECompositionState;
-using android::gui::ActivePicture;
-using android::gui::IActivePictureListener;
-using android::mock::MockLayer;
-using surfaceflinger::frontend::LayerSnapshot;
-using testing::_;
-using testing::NiceMock;
-using testing::Return;
-
-class TestableLayerFE : public LayerFE {
-public:
-    TestableLayerFE() : LayerFE("TestableLayerFE"), snapshot(*(new LayerSnapshot)) {
-        mSnapshot = std::unique_ptr<LayerSnapshot>(&snapshot);
-    }
-
-    LayerSnapshot& snapshot;
-};
-
-class ActivePictureUpdaterTest : public testing::Test {
-protected:
-    SurfaceFlinger* flinger() {
-        if (!mFlingerSetup) {
-            mFlinger.setupMockScheduler();
-            mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
-            mFlinger.setupRenderEngine(std::make_unique<renderengine::mock::RenderEngine>());
-            mFlingerSetup = true;
-        }
-        return mFlinger.flinger();
-    }
-
-private:
-    TestableSurfaceFlinger mFlinger;
-    bool mFlingerSetup = false;
-};
-
-// Hack to workaround initializer lists not working for parcelables because parcelables inherit from
-// Parcelable, which has a virtual destructor.
-auto UnorderedElementsAre(std::initializer_list<std::tuple<int32_t, int32_t, int64_t>> tuples) {
-    std::vector<ActivePicture> activePictures;
-    for (auto tuple : tuples) {
-        ActivePicture ap;
-        ap.layerId = std::get<0>(tuple);
-        ap.ownerUid = std::get<1>(tuple);
-        ap.pictureProfileId = std::get<2>(tuple);
-        activePictures.push_back(ap);
-    }
-    return testing::UnorderedElementsAreArray(activePictures);
-}
-
-// Parcelables don't define this for matchers, which is unfortunate
-void PrintTo(const ActivePicture& activePicture, std::ostream* os) {
-    *os << activePicture.toString();
-}
-
-TEST_F(ActivePictureUpdaterTest, notCalledWithNoProfile) {
-    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
-    TestableLayerFE layerFE;
-    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    ActivePictureUpdater updater;
-    {
-        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
-        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
-
-        ASSERT_FALSE(updater.updateAndHasChanged());
-    }
-}
-
-TEST_F(ActivePictureUpdaterTest, calledWhenLayerStartsUsingProfile) {
-    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
-    TestableLayerFE layerFE;
-    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    ActivePictureUpdater updater;
-    {
-        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
-        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
-
-        ASSERT_FALSE(updater.updateAndHasChanged());
-    }
-    {
-        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
-    }
-}
-
-TEST_F(ActivePictureUpdaterTest, notCalledWhenLayerContinuesUsingProfile) {
-    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
-    TestableLayerFE layerFE;
-    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    ActivePictureUpdater updater;
-    {
-        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
-    }
-    {
-        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
-
-        ASSERT_FALSE(updater.updateAndHasChanged());
-    }
-}
-
-TEST_F(ActivePictureUpdaterTest, calledWhenLayerStopsUsingProfile) {
-    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
-    TestableLayerFE layerFE;
-    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    ActivePictureUpdater updater;
-    {
-        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
-    }
-    {
-        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
-        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({}));
-    }
-}
-
-TEST_F(ActivePictureUpdaterTest, calledWhenLayerChangesProfile) {
-    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
-    TestableLayerFE layerFE;
-    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    ActivePictureUpdater updater;
-    {
-        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
-    }
-    {
-        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(2);
-        layerFE.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 2}}));
-    }
-}
-
-TEST_F(ActivePictureUpdaterTest, notCalledWhenUncommittedLayerChangesProfile) {
-    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
-    TestableLayerFE layerFE1;
-    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
-    TestableLayerFE layerFE2;
-    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(20)));
-
-    ActivePictureUpdater updater;
-    {
-        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE1.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
-
-        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
-    }
-    {
-        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE1.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
-
-        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
-        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
-
-        ASSERT_FALSE(updater.updateAndHasChanged());
-    }
-}
-
-TEST_F(ActivePictureUpdaterTest, calledWhenDifferentLayerUsesSameProfile) {
-    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
-    TestableLayerFE layerFE1;
-    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
-    TestableLayerFE layerFE2;
-    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(20)));
-
-    ActivePictureUpdater updater;
-    {
-        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE1.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
-
-        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
-        layerFE2.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(),
-                    UnorderedElementsAre({{100, 10, 1}, {200, 20, 2}}));
-    }
-    {
-        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(2);
-        layerFE1.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
-
-        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE2.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(),
-                    UnorderedElementsAre({{100, 10, 2}, {200, 20, 1}}));
-    }
-}
-
-TEST_F(ActivePictureUpdaterTest, calledWhenSameUidUsesSameProfile) {
-    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
-    TestableLayerFE layerFE1;
-    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
-    TestableLayerFE layerFE2;
-    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    ActivePictureUpdater updater;
-    {
-        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE1.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
-
-        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
-        layerFE2.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(),
-                    UnorderedElementsAre({{100, 10, 1}, {200, 10, 2}}));
-    }
-    {
-        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(2);
-        layerFE1.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
-
-        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE2.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(),
-                    UnorderedElementsAre({{100, 10, 2}, {200, 10, 1}}));
-    }
-}
-
-TEST_F(ActivePictureUpdaterTest, calledWhenNewLayerUsesSameProfile) {
-    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
-    TestableLayerFE layerFE1;
-    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    ActivePictureUpdater updater;
-    {
-        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE1.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
-    }
-
-    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
-    TestableLayerFE layerFE2;
-    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
-
-    {
-        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE1.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
-
-        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
-        layerFE2.onPictureProfileCommitted();
-        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
-
-        ASSERT_TRUE(updater.updateAndHasChanged());
-        EXPECT_THAT(updater.getActivePictures(),
-                    UnorderedElementsAre({{100, 10, 1}, {200, 10, 1}}));
-    }
-}
-
-} // namespace android
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/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 8c53eef..4d322ef 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -2021,16 +2021,13 @@
     EXPECT_FALSE(getSnapshot(1)->contentDirty);
 }
 TEST_F(LayerSnapshotTest, shouldUpdatePictureProfileHandle) {
-    if (!com_android_graphics_libgui_flags_apply_picture_profiles()) {
-        GTEST_SKIP() << "Flag disabled, skipping test";
-    }
     std::vector<TransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
-    transactions.back().states.front().layerId = 1;
-    transactions.back().states.front().state.layerId = 1;
-    transactions.back().states.front().state.what = layer_state_t::ePictureProfileHandleChanged;
-    transactions.back().states.front().state.pictureProfileHandle = PictureProfileHandle(3);
+    transactions.back().states.back().layerId = 1;
+    transactions.back().states.back().state.layerId = 1;
+    transactions.back().states.back().state.what = layer_state_t::ePictureProfileHandleChanged;
+    transactions.back().states.back().state.pictureProfileHandle = PictureProfileHandle(3);
 
     mLifecycleManager.applyTransactions(transactions);
     EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Content);
@@ -2042,23 +2039,50 @@
 }
 
 TEST_F(LayerSnapshotTest, shouldUpdatePictureProfilePriorityFromAppContentPriority) {
-    if (!com_android_graphics_libgui_flags_apply_picture_profiles()) {
-        GTEST_SKIP() << "Flag disabled, skipping test";
+    {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.back().layerId = 1;
+        transactions.back().states.back().state.layerId = 1;
+        transactions.back().states.back().state.what = layer_state_t::eAppContentPriorityChanged;
+        transactions.back().states.back().state.appContentPriority = 1;
+        transactions.back().states.push_back({});
+        transactions.back().states.back().layerId = 2;
+        transactions.back().states.back().state.layerId = 2;
+        transactions.back().states.back().state.what = layer_state_t::eAppContentPriorityChanged;
+        transactions.back().states.back().state.appContentPriority = -1;
+
+        mLifecycleManager.applyTransactions(transactions);
+        EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Content);
+
+        update(mSnapshotBuilder);
+
+        EXPECT_GT(getSnapshot(1)->pictureProfilePriority, getSnapshot(2)->pictureProfilePriority);
+        EXPECT_EQ(getSnapshot(1)->pictureProfilePriority - getSnapshot(2)->pictureProfilePriority,
+                  2);
     }
-    std::vector<TransactionState> transactions;
-    transactions.emplace_back();
-    transactions.back().states.push_back({});
-    transactions.back().states.front().layerId = 1;
-    transactions.back().states.front().state.layerId = 1;
-    transactions.back().states.front().state.what = layer_state_t::eAppContentPriorityChanged;
-    transactions.back().states.front().state.appContentPriority = 3;
+    {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.back().layerId = 1;
+        transactions.back().states.back().state.layerId = 1;
+        transactions.back().states.back().state.what = layer_state_t::eAppContentPriorityChanged;
+        transactions.back().states.back().state.appContentPriority = INT_MIN;
+        transactions.back().states.push_back({});
+        transactions.back().states.back().layerId = 2;
+        transactions.back().states.back().state.layerId = 2;
+        transactions.back().states.back().state.what = layer_state_t::eAppContentPriorityChanged;
+        transactions.back().states.back().state.appContentPriority = INT_MAX;
 
-    mLifecycleManager.applyTransactions(transactions);
-    EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Content);
+        mLifecycleManager.applyTransactions(transactions);
+        EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Content);
 
-    update(mSnapshotBuilder);
+        update(mSnapshotBuilder);
 
-    EXPECT_EQ(getSnapshot(1)->pictureProfilePriority, 3);
+        EXPECT_GT(getSnapshot(2)->pictureProfilePriority, getSnapshot(1)->pictureProfilePriority);
+    }
 }
 
 } // namespace android::surfaceflinger::frontend
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/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 0d5266e..2bf66ac 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -190,6 +190,9 @@
     MOCK_METHOD(Error, getMaxLayerPictureProfiles, (Display, int32_t*));
     MOCK_METHOD(Error, setDisplayPictureProfileId, (Display, PictureProfileId id));
     MOCK_METHOD(Error, setLayerPictureProfileId, (Display, Layer, PictureProfileId id));
+    MOCK_METHOD(Error, getLuts,
+                (Display, const std::vector<sp<GraphicBuffer>>&,
+                 std::vector<aidl::android::hardware::graphics::composer3::Luts>*));
 };
 
 } // namespace Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index ec065a7..4ca6fe0 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -116,6 +116,10 @@
     MOCK_METHOD(hal::Error, getMaxLayerPictureProfiles, (int32_t*), (override));
     MOCK_METHOD(hal::Error, setPictureProfileHandle, (const android::PictureProfileHandle&),
                 (override));
+    MOCK_METHOD(hal::Error, getLuts,
+                (const std::vector<android::sp<android::GraphicBuffer>>&,
+                 std::vector<aidl::android::hardware::graphics::composer3::Luts>*),
+                (override));
 };
 
 class Layer : public HWC2::Layer {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
index 88f83d2..7bd85cd 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
@@ -151,6 +151,9 @@
     MOCK_METHOD(int32_t, getMaxLayerPictureProfiles, (PhysicalDisplayId));
     MOCK_METHOD(status_t, setDisplayPictureProfileHandle,
                 (PhysicalDisplayId, const PictureProfileHandle&));
+    MOCK_METHOD(status_t, getLuts,
+                (PhysicalDisplayId, const std::vector<sp<GraphicBuffer>>&,
+                 std::vector<aidl::android::hardware::graphics::composer3::Luts>*));
 };
 
 } // namespace android::mock
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