Merge "SF: Activate display on boot and fold/unfold"
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 7770374..c0f3e30 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -1017,6 +1017,10 @@
             if (!reply && !acquireResult) goto finish;
             break;
 
+        case BR_TRANSACTION_PENDING_FROZEN:
+            ALOGW("Sending oneway calls to frozen process.");
+            goto finish;
+
         case BR_DEAD_REPLY:
             err = DEAD_OBJECT;
             goto finish;
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index a0c4334..2408307 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -444,7 +444,7 @@
     bool declared;
     if (Status status = mTheRealServiceManager->isDeclared(String8(name).c_str(), &declared);
         !status.isOk()) {
-        ALOGW("Failed to get isDeclard for %s: %s", String8(name).c_str(),
+        ALOGW("Failed to get isDeclared for %s: %s", String8(name).c_str(),
               status.toString8().c_str());
         return false;
     }
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 1f311ac..254dda8 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -439,6 +439,10 @@
     return mCurrentThreads;
 }
 
+bool ProcessState::isThreadPoolStarted() const {
+    return mThreadPoolStarted;
+}
+
 #define DRIVER_FEATURES_PATH "/dev/binderfs/features/"
 bool ProcessState::isDriverFeatureEnabled(const DriverFeature feature) {
     static const char* const names[] = {
diff --git a/libs/binder/binder_module.h b/libs/binder/binder_module.h
index 793795e..eef07ae 100644
--- a/libs/binder/binder_module.h
+++ b/libs/binder/binder_module.h
@@ -100,4 +100,9 @@
 #define BINDER_ENABLE_ONEWAY_SPAM_DETECTION _IOW('b', 16, __u32)
 #endif // BINDER_ENABLE_ONEWAY_SPAM_DETECTION
 
+#ifndef BR_TRANSACTION_PENDING_FROZEN
+// Temporary definition of BR_TRANSACTION_PENDING_FROZEN until UAPI binder.h includes it.
+#define BR_TRANSACTION_PENDING_FROZEN _IO('r', 20)
+#endif // BR_TRANSACTION_PENDING_FROZEN
+
 #endif // _BINDER_MODULE_H_
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index dc572ac..8cc8105 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -230,7 +230,6 @@
         "android.graphicsenv.IGpuService",
         "android.gui.IConsumerListener",
         "android.gui.IGraphicBufferConsumer",
-        "android.gui.ITransactionComposerListener",
         "android.gui.SensorEventConnection",
         "android.gui.SensorServer",
         "android.hardware.ICamera",
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index 87eee3d..bad8cb1 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -97,6 +97,11 @@
      */
     size_t getThreadPoolMaxTotalThreadCount() const;
 
+    /**
+     * Check to see if the thread pool has started.
+     */
+    bool isThreadPoolStarted() const;
+
     enum class DriverFeature {
         ONEWAY_SPAM_DETECTION,
         EXTENDED_ERROR,
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 4163897..1af2119 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -613,7 +613,8 @@
  *
  *  1. If the binder died, shortly after the call to onBinderDied.
  *  2. If the binder is explicitly unlinked with AIBinder_unlinkToDeath or
- *     AIBinder_DeathRecipient_delete.
+ *     AIBinder_DeathRecipient_delete, after any pending onBinderDied calls
+ *     finish.
  *  3. During or shortly after the AIBinder_linkToDeath call if it returns an error.
  *
  * It is guaranteed that the callback is called exactly once for each call to linkToDeath unless the
diff --git a/libs/binder/ndk/include_platform/android/binder_process.h b/libs/binder/ndk/include_platform/android/binder_process.h
index 8923129..ffcad55 100644
--- a/libs/binder/ndk/include_platform/android/binder_process.h
+++ b/libs/binder/ndk/include_platform/android/binder_process.h
@@ -43,6 +43,13 @@
  */
 bool ABinderProcess_setThreadPoolMaxThreadCount(uint32_t numThreads);
 /**
+ * Check if the threadpool has already been started.
+ * This tells whether someone in the process has called ABinderProcess_startThreadPool. Usually,
+ * you should use this in a library to abort if the threadpool is not started.
+ * Programs should configure binder threadpools once at the beginning.
+ */
+bool ABinderProcess_isThreadPoolStarted();
+/**
  * This adds the current thread to the threadpool. This may cause the threadpool to exceed the
  * maximum size.
  *
diff --git a/libs/binder/ndk/include_platform/android/binder_stability.h b/libs/binder/ndk/include_platform/android/binder_stability.h
index 683a433..c1f62e5 100644
--- a/libs/binder/ndk/include_platform/android/binder_stability.h
+++ b/libs/binder/ndk/include_platform/android/binder_stability.h
@@ -50,6 +50,15 @@
  * requirements associated with that higher stability level. For instance, a
  * VINTF stability binder is required to be in the VINTF manifest. This API
  * can be called to use that same interface within the vendor partition.
+ *
+ * WARNING: you must hold on to a binder instance after this is set, while you
+ * are using it. If you get a binder (e.g. `...->asBinder().get()`), you must
+ * save this binder and then
+ * use it. For instance:
+ *
+ *     auto binder = ...->asBinder();
+ *     AIBinder_forceDowngradeToVendorStability(binder.get());
+ *     doSomething(binder);
  */
 void AIBinder_forceDowngradeToVendorStability(AIBinder* binder);
 
@@ -79,6 +88,15 @@
  * requirements associated with that higher stability level. For instance, a
  * VINTF stability binder is required to be in the VINTF manifest. This API
  * can be called to use that same interface within the system partition.
+ *
+ * WARNING: you must hold on to a binder instance after this is set, while you
+ * are using it. If you get a binder (e.g. `...->asBinder().get()`), you must
+ * save this binder and then
+ * use it. For instance:
+ *
+ *     auto binder = ...->asBinder();
+ *     AIBinder_forceDowngradeToSystemStability(binder.get());
+ *     doSomething(binder);
  */
 void AIBinder_forceDowngradeToSystemStability(AIBinder* binder);
 
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 5c7005c..54e4628 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -154,6 +154,7 @@
 
 LIBBINDER_NDK34 { # introduced=UpsideDownCake
   global:
+    ABinderProcess_isThreadPoolStarted; # systemapi llndk
     AServiceManager_getUpdatableApexName; # systemapi
     AServiceManager_registerForServiceNotifications; # systemapi llndk
     AServiceManager_NotificationRegistration_delete; # systemapi llndk
diff --git a/libs/binder/ndk/process.cpp b/libs/binder/ndk/process.cpp
index ac582a4..bc6610e 100644
--- a/libs/binder/ndk/process.cpp
+++ b/libs/binder/ndk/process.cpp
@@ -31,6 +31,9 @@
 bool ABinderProcess_setThreadPoolMaxThreadCount(uint32_t numThreads) {
     return ProcessState::self()->setThreadPoolMaxThreadCount(numThreads) == 0;
 }
+bool ABinderProcess_isThreadPoolStarted() {
+    return ProcessState::self()->isThreadPoolStarted();
+}
 void ABinderProcess_joinThreadPool() {
     IPCThreadState::self()->joinThreadPool();
 }
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 25b524f..f7498c4 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -120,6 +120,7 @@
     BINDER_LIB_TEST_CAN_GET_SID,
     BINDER_LIB_TEST_GET_MAX_THREAD_COUNT,
     BINDER_LIB_TEST_SET_MAX_THREAD_COUNT,
+    BINDER_LIB_TEST_IS_THREADPOOL_STARTED,
     BINDER_LIB_TEST_LOCK_UNLOCK,
     BINDER_LIB_TEST_PROCESS_LOCK,
     BINDER_LIB_TEST_UNLOCK_AFTER_MS,
@@ -1383,6 +1384,14 @@
     EXPECT_EQ(replyi, kKernelThreads + 1);
 }
 
+TEST_F(BinderLibTest, ThreadPoolStarted) {
+    Parcel data, reply;
+    sp<IBinder> server = addServer();
+    ASSERT_TRUE(server != nullptr);
+    EXPECT_THAT(server->transact(BINDER_LIB_TEST_IS_THREADPOOL_STARTED, data, &reply), NO_ERROR);
+    EXPECT_TRUE(reply.readBool());
+}
+
 size_t epochMillis() {
     using std::chrono::duration_cast;
     using std::chrono::milliseconds;
@@ -1849,6 +1858,10 @@
                 reply->writeInt32(ProcessState::self()->getThreadPoolMaxTotalThreadCount());
                 return NO_ERROR;
             }
+            case BINDER_LIB_TEST_IS_THREADPOOL_STARTED: {
+                reply->writeBool(ProcessState::self()->isThreadPoolStarted());
+                return NO_ERROR;
+            }
             case BINDER_LIB_TEST_PROCESS_LOCK: {
                 m_blockMutex.lock();
                 return NO_ERROR;
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
index 86461c8..8bef33f 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
@@ -37,7 +37,9 @@
     }
 
     while (provider.remaining_bytes() > 0) {
-        uint32_t code = provider.ConsumeIntegral<uint32_t>();
+        // Most of the AIDL services will have small set of transaction codes.
+        uint32_t code = provider.ConsumeBool() ? provider.ConsumeIntegral<uint32_t>()
+                                               : provider.ConsumeIntegralInRange<uint32_t>(0, 100);
         uint32_t flags = provider.ConsumeIntegral<uint32_t>();
         Parcel data;
         // for increased fuzz coverage
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index a77ca04..a0e75ff 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -42,6 +42,7 @@
 
 namespace android {
 
+using gui::CallbackId;
 using gui::DisplayCaptureArgs;
 using gui::IDisplayEventConnection;
 using gui::IRegionSamplingListener;
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index 2b25b61..23d7d50 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -21,22 +21,11 @@
 #include <optional>
 
 #include <gui/ISurfaceComposer.h>
-#include <gui/ITransactionCompletedListener.h>
 #include <gui/LayerState.h>
+#include <gui/ListenerStats.h>
 #include <private/gui/ParcelUtils.h>
 
-namespace android {
-
-namespace { // Anonymous
-
-enum class Tag : uint32_t {
-    ON_TRANSACTION_COMPLETED = IBinder::FIRST_CALL_TRANSACTION,
-    ON_RELEASE_BUFFER,
-    ON_TRANSACTION_QUEUE_STALLED,
-    LAST = ON_TRANSACTION_QUEUE_STALLED,
-};
-
-} // Anonymous namespace
+namespace android::gui {
 
 status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const {
     status_t err = output->writeUint64(frameNumber);
@@ -274,60 +263,6 @@
     return listenerStats;
 }
 
-class BpTransactionCompletedListener : public SafeBpInterface<ITransactionCompletedListener> {
-public:
-    explicit BpTransactionCompletedListener(const sp<IBinder>& impl)
-          : SafeBpInterface<ITransactionCompletedListener>(impl, "BpTransactionCompletedListener") {
-    }
-
-    ~BpTransactionCompletedListener() override;
-
-    void onTransactionCompleted(ListenerStats stats) override {
-        callRemoteAsync<decltype(&ITransactionCompletedListener::
-                                         onTransactionCompleted)>(Tag::ON_TRANSACTION_COMPLETED,
-                                                                  stats);
-    }
-
-    void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
-                         uint32_t currentMaxAcquiredBufferCount) override {
-        callRemoteAsync<decltype(&ITransactionCompletedListener::
-                                         onReleaseBuffer)>(Tag::ON_RELEASE_BUFFER, callbackId,
-                                                           releaseFence,
-                                                           currentMaxAcquiredBufferCount);
-    }
-
-    void onTransactionQueueStalled(const String8& reason) override {
-        callRemoteAsync<
-                decltype(&ITransactionCompletedListener::
-                                 onTransactionQueueStalled)>(Tag::ON_TRANSACTION_QUEUE_STALLED,
-                                                             reason);
-    }
-};
-
-// Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see
-// clang warning -Wweak-vtables)
-BpTransactionCompletedListener::~BpTransactionCompletedListener() = default;
-
-IMPLEMENT_META_INTERFACE(TransactionCompletedListener, "android.gui.ITransactionComposerListener");
-
-status_t BnTransactionCompletedListener::onTransact(uint32_t code, const Parcel& data,
-                                                    Parcel* reply, uint32_t flags) {
-    if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast<uint32_t>(Tag::LAST)) {
-        return BBinder::onTransact(code, data, reply, flags);
-    }
-    auto tag = static_cast<Tag>(code);
-    switch (tag) {
-        case Tag::ON_TRANSACTION_COMPLETED:
-            return callLocalAsync(data, reply,
-                                  &ITransactionCompletedListener::onTransactionCompleted);
-        case Tag::ON_RELEASE_BUFFER:
-            return callLocalAsync(data, reply, &ITransactionCompletedListener::onReleaseBuffer);
-        case Tag::ON_TRANSACTION_QUEUE_STALLED:
-            return callLocalAsync(data, reply,
-                                  &ITransactionCompletedListener::onTransactionQueueStalled);
-    }
-}
-
 ListenerCallbacks ListenerCallbacks::filter(CallbackId::Type type) const {
     std::vector<CallbackId> filteredCallbackIds;
     for (const auto& callbackId : callbackIds) {
@@ -366,4 +301,4 @@
 
 const ReleaseCallbackId ReleaseCallbackId::INVALID_ID = ReleaseCallbackId(0, 0);
 
-}; // namespace android
+}; // namespace android::gui
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 95962af..0d1a69b 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -51,6 +51,7 @@
 
 namespace android {
 
+using gui::CallbackId;
 using gui::FocusRequest;
 using gui::WindowInfoHandle;
 
@@ -388,6 +389,27 @@
     }
 }
 
+void DisplayState::sanitize(int32_t permissions) {
+    if (what & DisplayState::eLayerStackChanged) {
+        if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~DisplayState::eLayerStackChanged;
+            ALOGE("Stripped attempt to set eLayerStackChanged in sanitize");
+        }
+    }
+    if (what & DisplayState::eDisplayProjectionChanged) {
+        if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~DisplayState::eDisplayProjectionChanged;
+            ALOGE("Stripped attempt to set eDisplayProjectionChanged in sanitize");
+        }
+    }
+    if (what & DisplayState::eSurfaceChanged) {
+        if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~DisplayState::eSurfaceChanged;
+            ALOGE("Stripped attempt to set eSurfaceChanged in sanitize");
+        }
+    }
+}
+
 void layer_state_t::sanitize(int32_t permissions) {
     // TODO: b/109894387
     //
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 325c294..d741c99 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -314,7 +314,8 @@
     }
 }
 
-void TransactionCompletedListener::onTransactionCompleted(ListenerStats listenerStats) {
+binder::Status TransactionCompletedListener::onTransactionCompleted(
+        const ListenerStats& listenerStats) {
     std::unordered_map<CallbackId, CallbackTranslation, CallbackIdHash> callbacksMap;
     std::multimap<int32_t, sp<JankDataListener>> jankListenersMap;
     {
@@ -454,9 +455,10 @@
             }
         }
     }
+    return binder::Status::ok();
 }
 
-void TransactionCompletedListener::onTransactionQueueStalled(const String8& reason) {
+binder::Status TransactionCompletedListener::onTransactionQueueStalled(const std::string& reason) {
     std::unordered_map<void*, std::function<void(const std::string&)>> callbackCopy;
     {
         std::scoped_lock<std::mutex> lock(mMutex);
@@ -465,6 +467,7 @@
     for (auto const& it : callbackCopy) {
         it.second(reason.c_str());
     }
+    return binder::Status::ok();
 }
 
 void TransactionCompletedListener::addQueueStallListener(
@@ -478,9 +481,12 @@
     mQueueStallListeners.erase(id);
 }
 
-void TransactionCompletedListener::onReleaseBuffer(ReleaseCallbackId callbackId,
-                                                   sp<Fence> releaseFence,
-                                                   uint32_t currentMaxAcquiredBufferCount) {
+binder::Status TransactionCompletedListener::onReleaseBuffer(
+        const ReleaseCallbackId& callbackId,
+        const std::optional<os::ParcelFileDescriptor>& releaseFenceFd,
+        int32_t currentMaxAcquiredBufferCount) {
+    sp<Fence> releaseFence(releaseFenceFd ? new Fence(::dup(releaseFenceFd->get()))
+                                          : Fence::NO_FENCE);
     ReleaseBufferCallback callback;
     {
         std::scoped_lock<std::mutex> lock(mMutex);
@@ -489,13 +495,14 @@
     if (!callback) {
         ALOGE("Could not call release buffer callback, buffer not found %s",
               callbackId.to_string().c_str());
-        return;
+        return binder::Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT);
     }
     std::optional<uint32_t> optionalMaxAcquiredBufferCount =
-            currentMaxAcquiredBufferCount == UINT_MAX
+            static_cast<uint32_t>(currentMaxAcquiredBufferCount) == UINT_MAX
             ? std::nullopt
             : std::make_optional<uint32_t>(currentMaxAcquiredBufferCount);
     callback(callbackId, releaseFence, optionalMaxAcquiredBufferCount);
+    return binder::Status::ok();
 }
 
 ReleaseBufferCallback TransactionCompletedListener::popReleaseBufferCallbackLocked(
@@ -825,7 +832,11 @@
                 ->mReleaseCallbackThread
                 .addReleaseCallback(state.bufferData->generateReleaseCallbackId(), fence);
     } else {
-        listener->onReleaseBuffer(state.bufferData->generateReleaseCallbackId(), fence, UINT_MAX);
+        std::optional<os::ParcelFileDescriptor> fenceFd;
+        if (fence != Fence::NO_FENCE) {
+            fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(fence->get())));
+        }
+        listener->onReleaseBuffer(state.bufferData->generateReleaseCallbackId(), fenceFd, UINT_MAX);
     }
 }
 
@@ -2260,12 +2271,12 @@
     return statusTFromBinderStatus(status);
 }
 
-status_t SurfaceComposerClient::getStaticDisplayInfo(const sp<IBinder>& display,
+status_t SurfaceComposerClient::getStaticDisplayInfo(int64_t displayId,
                                                      ui::StaticDisplayInfo* outInfo) {
     using Tag = android::gui::DeviceProductInfo::ManufactureOrModelDate::Tag;
     gui::StaticDisplayInfo ginfo;
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->getStaticDisplayInfo(display, &ginfo);
+            ComposerServiceAIDL::getComposerService()->getStaticDisplayInfo(displayId, &ginfo);
     if (status.isOk()) {
         // convert gui::StaticDisplayInfo to ui::StaticDisplayInfo
         outInfo->connectionType = static_cast<ui::DisplayConnectionType>(ginfo.connectionType);
@@ -2309,56 +2320,74 @@
     return statusTFromBinderStatus(status);
 }
 
-status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp<IBinder>& display,
-                                                      ui::DynamicDisplayInfo* outInfo) {
+void SurfaceComposerClient::getDynamicDisplayInfoInternal(gui::DynamicDisplayInfo& ginfo,
+                                                          ui::DynamicDisplayInfo*& outInfo) {
+    // convert gui::DynamicDisplayInfo to ui::DynamicDisplayInfo
+    outInfo->supportedDisplayModes.clear();
+    outInfo->supportedDisplayModes.reserve(ginfo.supportedDisplayModes.size());
+    for (const auto& mode : ginfo.supportedDisplayModes) {
+        ui::DisplayMode outMode;
+        outMode.id = mode.id;
+        outMode.resolution.width = mode.resolution.width;
+        outMode.resolution.height = mode.resolution.height;
+        outMode.xDpi = mode.xDpi;
+        outMode.yDpi = mode.yDpi;
+        outMode.refreshRate = mode.refreshRate;
+        outMode.appVsyncOffset = mode.appVsyncOffset;
+        outMode.sfVsyncOffset = mode.sfVsyncOffset;
+        outMode.presentationDeadline = mode.presentationDeadline;
+        outMode.group = mode.group;
+        std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(),
+                       std::back_inserter(outMode.supportedHdrTypes),
+                       [](const int32_t& value) { return static_cast<ui::Hdr>(value); });
+        outInfo->supportedDisplayModes.push_back(outMode);
+    }
+
+    outInfo->activeDisplayModeId = ginfo.activeDisplayModeId;
+    outInfo->renderFrameRate = ginfo.renderFrameRate;
+
+    outInfo->supportedColorModes.clear();
+    outInfo->supportedColorModes.reserve(ginfo.supportedColorModes.size());
+    for (const auto& cmode : ginfo.supportedColorModes) {
+        outInfo->supportedColorModes.push_back(static_cast<ui::ColorMode>(cmode));
+    }
+
+    outInfo->activeColorMode = static_cast<ui::ColorMode>(ginfo.activeColorMode);
+
+    std::vector<ui::Hdr> types;
+    types.reserve(ginfo.hdrCapabilities.supportedHdrTypes.size());
+    for (const auto& hdr : ginfo.hdrCapabilities.supportedHdrTypes) {
+        types.push_back(static_cast<ui::Hdr>(hdr));
+    }
+    outInfo->hdrCapabilities = HdrCapabilities(types, ginfo.hdrCapabilities.maxLuminance,
+                                               ginfo.hdrCapabilities.maxAverageLuminance,
+                                               ginfo.hdrCapabilities.minLuminance);
+
+    outInfo->autoLowLatencyModeSupported = ginfo.autoLowLatencyModeSupported;
+    outInfo->gameContentTypeSupported = ginfo.gameContentTypeSupported;
+    outInfo->preferredBootDisplayMode = ginfo.preferredBootDisplayMode;
+}
+
+status_t SurfaceComposerClient::getDynamicDisplayInfoFromId(int64_t displayId,
+                                                            ui::DynamicDisplayInfo* outInfo) {
     gui::DynamicDisplayInfo ginfo;
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->getDynamicDisplayInfo(display, &ginfo);
+            ComposerServiceAIDL::getComposerService()->getDynamicDisplayInfoFromId(displayId,
+                                                                                   &ginfo);
     if (status.isOk()) {
-        // convert gui::DynamicDisplayInfo to ui::DynamicDisplayInfo
-        outInfo->supportedDisplayModes.clear();
-        outInfo->supportedDisplayModes.reserve(ginfo.supportedDisplayModes.size());
-        for (const auto& mode : ginfo.supportedDisplayModes) {
-            ui::DisplayMode outMode;
-            outMode.id = mode.id;
-            outMode.resolution.width = mode.resolution.width;
-            outMode.resolution.height = mode.resolution.height;
-            outMode.xDpi = mode.xDpi;
-            outMode.yDpi = mode.yDpi;
-            outMode.refreshRate = mode.refreshRate;
-            outMode.appVsyncOffset = mode.appVsyncOffset;
-            outMode.sfVsyncOffset = mode.sfVsyncOffset;
-            outMode.presentationDeadline = mode.presentationDeadline;
-            outMode.group = mode.group;
-            std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(),
-                           std::back_inserter(outMode.supportedHdrTypes),
-                           [](const int32_t& value) { return static_cast<ui::Hdr>(value); });
-            outInfo->supportedDisplayModes.push_back(outMode);
-        }
+        getDynamicDisplayInfoInternal(ginfo, outInfo);
+    }
+    return statusTFromBinderStatus(status);
+}
 
-        outInfo->activeDisplayModeId = ginfo.activeDisplayModeId;
-        outInfo->renderFrameRate = ginfo.renderFrameRate;
-
-        outInfo->supportedColorModes.clear();
-        outInfo->supportedColorModes.reserve(ginfo.supportedColorModes.size());
-        for (const auto& cmode : ginfo.supportedColorModes) {
-            outInfo->supportedColorModes.push_back(static_cast<ui::ColorMode>(cmode));
-        }
-
-        outInfo->activeColorMode = static_cast<ui::ColorMode>(ginfo.activeColorMode);
-
-        std::vector<ui::Hdr> types;
-        types.reserve(ginfo.hdrCapabilities.supportedHdrTypes.size());
-        for (const auto& hdr : ginfo.hdrCapabilities.supportedHdrTypes) {
-            types.push_back(static_cast<ui::Hdr>(hdr));
-        }
-        outInfo->hdrCapabilities = HdrCapabilities(types, ginfo.hdrCapabilities.maxLuminance,
-                                                   ginfo.hdrCapabilities.maxAverageLuminance,
-                                                   ginfo.hdrCapabilities.minLuminance);
-
-        outInfo->autoLowLatencyModeSupported = ginfo.autoLowLatencyModeSupported;
-        outInfo->gameContentTypeSupported = ginfo.gameContentTypeSupported;
-        outInfo->preferredBootDisplayMode = ginfo.preferredBootDisplayMode;
+status_t SurfaceComposerClient::getDynamicDisplayInfoFromToken(const sp<IBinder>& display,
+                                                               ui::DynamicDisplayInfo* outInfo) {
+    gui::DynamicDisplayInfo ginfo;
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getDynamicDisplayInfoFromToken(display,
+                                                                                      &ginfo);
+    if (status.isOk()) {
+        getDynamicDisplayInfoInternal(ginfo, outInfo);
     }
     return statusTFromBinderStatus(status);
 }
@@ -2366,7 +2395,8 @@
 status_t SurfaceComposerClient::getActiveDisplayMode(const sp<IBinder>& display,
                                                      ui::DisplayMode* mode) {
     ui::DynamicDisplayInfo info;
-    status_t result = getDynamicDisplayInfo(display, &info);
+
+    status_t result = getDynamicDisplayInfoFromToken(display, &info);
     if (result != NO_ERROR) {
         return result;
     }
@@ -2542,7 +2572,7 @@
     gui::PullAtomData pad;
     binder::Status status = ComposerServiceAIDL::getComposerService()->onPullAtom(atomId, &pad);
     if (status.isOk()) {
-        outData->assign((const char*)pad.data.data(), pad.data.size());
+        outData->assign(pad.data.begin(), pad.data.end());
         *success = pad.success;
     }
     return statusTFromBinderStatus(status);
@@ -2827,7 +2857,11 @@
 
         while (!callbackInfos.empty()) {
             auto [callbackId, releaseFence] = callbackInfos.front();
-            listener->onReleaseBuffer(callbackId, std::move(releaseFence), UINT_MAX);
+            std::optional<os::ParcelFileDescriptor> fenceFd;
+            if (releaseFence != Fence::NO_FENCE) {
+                fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(releaseFence->get())));
+            }
+            listener->onReleaseBuffer(callbackId, fenceFd, UINT_MAX);
             callbackInfos.pop();
         }
 
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index 40410fb..488a148 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -126,12 +126,14 @@
     /**
      * Gets immutable information about given physical display.
      */
-    StaticDisplayInfo getStaticDisplayInfo(IBinder display);
+    StaticDisplayInfo getStaticDisplayInfo(long displayId);
 
     /**
      * Gets dynamic information about given physical display.
      */
-    DynamicDisplayInfo getDynamicDisplayInfo(IBinder display);
+    DynamicDisplayInfo getDynamicDisplayInfoFromId(long displayId);
+
+    DynamicDisplayInfo getDynamicDisplayInfoFromToken(IBinder display);
 
     DisplayPrimaries getDisplayNativePrimaries(IBinder display);
 
diff --git a/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl b/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl
new file mode 100644
index 0000000..dde4d38
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2022 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.
+ */
+
+package android.gui;
+
+import android.gui.ListenerStats;
+import android.gui.ReleaseCallbackId;
+
+/** @hide */
+oneway interface ITransactionCompletedListener {
+   void onTransactionCompleted(in ListenerStats stats);
+
+   void onReleaseBuffer(in ReleaseCallbackId callbackId,
+                        in @nullable ParcelFileDescriptor releaseFenceFd,
+                        int currentMaxAcquiredBufferCount);
+
+   void onTransactionQueueStalled(@utf8InCpp String name);
+}
diff --git a/libs/gui/aidl/android/gui/ListenerStats.aidl b/libs/gui/aidl/android/gui/ListenerStats.aidl
new file mode 100644
index 0000000..63248b2
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ListenerStats.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 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.
+ */
+
+package android.gui;
+
+parcelable ListenerStats cpp_header "gui/ListenerStats.h";
diff --git a/libs/gui/aidl/android/gui/PullAtomData.aidl b/libs/gui/aidl/android/gui/PullAtomData.aidl
index 14d33c6..c307cef 100644
--- a/libs/gui/aidl/android/gui/PullAtomData.aidl
+++ b/libs/gui/aidl/android/gui/PullAtomData.aidl
@@ -18,6 +18,6 @@
 
 /** @hide */
 parcelable PullAtomData {
-    @utf8InCpp String data;
+    byte[] data;
     boolean success;
 }
diff --git a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl b/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
new file mode 100644
index 0000000..c86de34
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 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.
+ */
+
+package android.gui;
+
+parcelable ReleaseCallbackId cpp_header "gui/ReleaseCallbackId.h";
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 9d1ee8f..8810e4e 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -79,9 +79,11 @@
                 (override));
     MOCK_METHOD(binder::Status, getDisplayState, (const sp<IBinder>&, gui::DisplayState*),
                 (override));
-    MOCK_METHOD(binder::Status, getStaticDisplayInfo, (const sp<IBinder>&, gui::StaticDisplayInfo*),
+    MOCK_METHOD(binder::Status, getStaticDisplayInfo, (int64_t, gui::StaticDisplayInfo*),
                 (override));
-    MOCK_METHOD(binder::Status, getDynamicDisplayInfo,
+    MOCK_METHOD(binder::Status, getDynamicDisplayInfoFromId, (int64_t, gui::DynamicDisplayInfo*),
+                (override));
+    MOCK_METHOD(binder::Status, getDynamicDisplayInfoFromToken,
                 (const sp<IBinder>&, gui::DynamicDisplayInfo*), (override));
     MOCK_METHOD(binder::Status, getDisplayNativePrimaries,
                 (const sp<IBinder>&, gui::DisplayPrimaries*), (override));
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index d517e99..d70a7f0 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -23,11 +23,11 @@
 #include <android/gui/IHdrLayerInfoListener.h>
 #include <android/gui/IRegionSamplingListener.h>
 #include <android/gui/IScreenCaptureListener.h>
+#include <android/gui/ITransactionCompletedListener.h>
 #include <android/gui/ITunnelModeEnabledListener.h>
 #include <android/gui/IWindowInfosListener.h>
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
-#include <gui/ITransactionCompletedListener.h>
 #include <gui/SpHash.h>
 #include <math/vec4.h>
 #include <stdint.h>
@@ -66,6 +66,7 @@
 using gui::IDisplayEventConnection;
 using gui::IRegionSamplingListener;
 using gui::IScreenCaptureListener;
+using gui::ListenerCallbacks;
 using gui::SpHash;
 
 namespace gui {
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 6ec6bd7..c5fdf82 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -21,10 +21,10 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include <android/gui/ITransactionCompletedListener.h>
 #include <android/gui/IWindowInfosReportedListener.h>
 #include <android/native_window.h>
 #include <gui/IGraphicBufferProducer.h>
-#include <gui/ITransactionCompletedListener.h>
 #include <math/mat4.h>
 
 #include <android/gui/DropInputMode.h>
@@ -35,6 +35,7 @@
 #include <gui/ISurfaceComposer.h>
 #include <gui/LayerCaptureArgs.h>
 #include <gui/LayerMetadata.h>
+#include <gui/ReleaseCallbackId.h>
 #include <gui/SpHash.h>
 #include <gui/SurfaceControl.h>
 #include <gui/WindowInfo.h>
@@ -56,6 +57,9 @@
 using gui::ISurfaceComposerClient;
 using gui::LayerMetadata;
 
+using gui::ITransactionCompletedListener;
+using gui::ReleaseCallbackId;
+
 struct client_cache_t {
     wp<IBinder> token = nullptr;
     uint64_t id;
@@ -359,6 +363,7 @@
 
     DisplayState();
     void merge(const DisplayState& other);
+    void sanitize(int32_t permissions);
 
     uint32_t what = 0;
     uint32_t flags = 0;
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ListenerStats.h
similarity index 81%
rename from libs/gui/include/gui/ITransactionCompletedListener.h
rename to libs/gui/include/gui/ListenerStats.h
index 453e8f3..3a12802 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ListenerStats.h
@@ -24,6 +24,8 @@
 #include <binder/SafeInterface.h>
 
 #include <gui/FrameTimestamps.h>
+#include <gui/ReleaseCallbackId.h>
+
 #include <ui/Fence.h>
 #include <utils/Timers.h>
 
@@ -32,10 +34,7 @@
 #include <unordered_set>
 #include <variant>
 
-namespace android {
-
-class ITransactionCompletedListener;
-class ListenerCallbacks;
+namespace android::gui {
 
 class CallbackId : public Parcelable {
 public:
@@ -54,30 +53,6 @@
     std::size_t operator()(const CallbackId& key) const { return std::hash<int64_t>()(key.id); }
 };
 
-class ReleaseCallbackId : public Parcelable {
-public:
-    static const ReleaseCallbackId INVALID_ID;
-
-    uint64_t bufferId;
-    uint64_t framenumber;
-    ReleaseCallbackId() {}
-    ReleaseCallbackId(uint64_t bufferId, uint64_t framenumber)
-          : bufferId(bufferId), framenumber(framenumber) {}
-    status_t writeToParcel(Parcel* output) const override;
-    status_t readFromParcel(const Parcel* input) override;
-
-    bool operator==(const ReleaseCallbackId& rhs) const {
-        return bufferId == rhs.bufferId && framenumber == rhs.framenumber;
-    }
-    bool operator!=(const ReleaseCallbackId& rhs) const { return !operator==(rhs); }
-    std::string to_string() const {
-        if (*this == INVALID_ID) return "INVALID_ID";
-
-        return "bufferId:" + std::to_string(bufferId) +
-                " framenumber:" + std::to_string(framenumber);
-    }
-};
-
 struct ReleaseBufferCallbackIdHash {
     std::size_t operator()(const ReleaseCallbackId& key) const {
         return std::hash<uint64_t>()(key.bufferId);
@@ -186,27 +161,6 @@
     std::vector<TransactionStats> transactionStats;
 };
 
-class ITransactionCompletedListener : public IInterface {
-public:
-    DECLARE_META_INTERFACE(TransactionCompletedListener)
-
-    virtual void onTransactionCompleted(ListenerStats stats) = 0;
-
-    virtual void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
-                                 uint32_t currentMaxAcquiredBufferCount) = 0;
-
-    virtual void onTransactionQueueStalled(const String8& name) = 0;
-};
-
-class BnTransactionCompletedListener : public SafeBnInterface<ITransactionCompletedListener> {
-public:
-    BnTransactionCompletedListener()
-          : SafeBnInterface<ITransactionCompletedListener>("BnTransactionCompletedListener") {}
-
-    status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                        uint32_t flags = 0) override;
-};
-
 class ListenerCallbacks {
 public:
     ListenerCallbacks(const sp<IBinder>& listener,
@@ -268,4 +222,4 @@
     }
 };
 
-} // namespace android
+} // namespace android::gui
diff --git a/libs/gui/include/gui/ReleaseCallbackId.h b/libs/gui/include/gui/ReleaseCallbackId.h
new file mode 100644
index 0000000..142ee5a
--- /dev/null
+++ b/libs/gui/include/gui/ReleaseCallbackId.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 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 <binder/Parcel.h>
+#include <binder/Parcelable.h>
+
+#include <cstdint>
+
+namespace android::gui {
+
+class ReleaseCallbackId : public Parcelable {
+public:
+    static const ReleaseCallbackId INVALID_ID;
+
+    uint64_t bufferId;
+    uint64_t framenumber;
+    ReleaseCallbackId() {}
+    ReleaseCallbackId(uint64_t bufferId, uint64_t framenumber)
+          : bufferId(bufferId), framenumber(framenumber) {}
+    status_t writeToParcel(Parcel* output) const override;
+    status_t readFromParcel(const Parcel* input) override;
+
+    bool operator==(const ReleaseCallbackId& rhs) const {
+        return bufferId == rhs.bufferId && framenumber == rhs.framenumber;
+    }
+    bool operator!=(const ReleaseCallbackId& rhs) const { return !operator==(rhs); }
+    std::string to_string() const {
+        if (*this == INVALID_ID) return "INVALID_ID";
+
+        return "bufferId:" + std::to_string(bufferId) +
+                " framenumber:" + std::to_string(framenumber);
+    }
+};
+
+} // namespace android::gui
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 2038f14..96d3a23 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -42,10 +42,13 @@
 
 #include <android/gui/ISurfaceComposerClient.h>
 
+#include <android/gui/BnTransactionCompletedListener.h>
+
 #include <gui/CpuConsumer.h>
 #include <gui/ISurfaceComposer.h>
-#include <gui/ITransactionCompletedListener.h>
 #include <gui/LayerState.h>
+#include <gui/ListenerStats.h>
+#include <gui/ReleaseCallbackId.h>
 #include <gui/SurfaceControl.h>
 #include <gui/WindowInfosListenerReporter.h>
 #include <math/vec3.h>
@@ -59,11 +62,21 @@
 class ITunnelModeEnabledListener;
 class Region;
 
+using gui::BnTransactionCompletedListener;
+using gui::CallbackId;
+using gui::CallbackIdHash;
 using gui::DisplayCaptureArgs;
+using gui::FrameEventHistoryStats;
 using gui::IRegionSamplingListener;
 using gui::ISurfaceComposerClient;
+using gui::ITransactionCompletedListener;
+using gui::JankData;
 using gui::LayerCaptureArgs;
 using gui::LayerMetadata;
+using gui::ListenerStats;
+using gui::ReleaseBufferCallbackIdHash;
+using gui::ReleaseCallbackId;
+using gui::SurfaceStats;
 
 struct SurfaceControlStats {
     SurfaceControlStats(const sp<SurfaceControl>& sc, nsecs_t latchTime,
@@ -149,10 +162,10 @@
     static status_t getDisplayState(const sp<IBinder>& display, ui::DisplayState*);
 
     // Get immutable information about given physical display.
-    static status_t getStaticDisplayInfo(const sp<IBinder>& display, ui::StaticDisplayInfo*);
+    static status_t getStaticDisplayInfo(int64_t, ui::StaticDisplayInfo*);
 
-    // Get dynamic information about given physical display.
-    static status_t getDynamicDisplayInfo(const sp<IBinder>& display, ui::DynamicDisplayInfo*);
+    // Get dynamic information about given physical display from display id
+    static status_t getDynamicDisplayInfoFromId(int64_t, ui::DynamicDisplayInfo*);
 
     // Shorthand for the active display mode from getDynamicDisplayInfo().
     // TODO(b/180391891): Update clients to use getDynamicDisplayInfo and remove this function.
@@ -714,6 +727,12 @@
     ReleaseCallbackThread mReleaseCallbackThread;
 
 private:
+    // Get dynamic information about given physical display from token
+    static status_t getDynamicDisplayInfoFromToken(const sp<IBinder>& display,
+                                                   ui::DynamicDisplayInfo*);
+
+    static void getDynamicDisplayInfoInternal(gui::DynamicDisplayInfo& ginfo,
+                                              ui::DynamicDisplayInfo*& outInfo);
     virtual void onFirstRef();
 
     mutable     Mutex                       mLock;
@@ -819,17 +838,17 @@
     void setReleaseBufferCallback(const ReleaseCallbackId&, ReleaseBufferCallback);
 
     // BnTransactionCompletedListener overrides
-    void onTransactionCompleted(ListenerStats stats) override;
-    void onReleaseBuffer(ReleaseCallbackId, sp<Fence> releaseFence,
-                         uint32_t currentMaxAcquiredBufferCount) override;
+    binder::Status onTransactionCompleted(const ListenerStats& stats) override;
+    binder::Status onReleaseBuffer(const ReleaseCallbackId& callbackId,
+                                   const std::optional<os::ParcelFileDescriptor>& releaseFenceFd,
+                                   int32_t currentMaxAcquiredBufferCount) override;
+    binder::Status onTransactionQueueStalled(const std::string& reason) override;
 
     void removeReleaseBufferCallback(const ReleaseCallbackId& callbackId);
 
     // For Testing Only
     static void setInstance(const sp<TransactionCompletedListener>&);
 
-    void onTransactionQueueStalled(const String8& reason) override;
-
 private:
     ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&);
     static sp<TransactionCompletedListener> sInstance;
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 6d3b425..55242df 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -782,13 +782,18 @@
         return binder::Status::ok();
     }
 
-    binder::Status getStaticDisplayInfo(const sp<IBinder>& /*display*/,
+    binder::Status getStaticDisplayInfo(int64_t /*displayId*/,
                                         gui::StaticDisplayInfo* /*outInfo*/) override {
         return binder::Status::ok();
     }
 
-    binder::Status getDynamicDisplayInfo(const sp<IBinder>& /*display*/,
-                                         gui::DynamicDisplayInfo* /*outInfo*/) override {
+    binder::Status getDynamicDisplayInfoFromId(int64_t /*displayId*/,
+                                               gui::DynamicDisplayInfo* /*outInfo*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status getDynamicDisplayInfoFromToken(const sp<IBinder>& /*display*/,
+                                                  gui::DynamicDisplayInfo* /*outInfo*/) override {
         return binder::Status::ok();
     }
 
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
index 9f53a57..6995762 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -44,6 +44,7 @@
     ERROR_JPEGR_DECODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 2,
     ERROR_JPEGR_CALCULATION_ERROR       = JPEGR_RUNTIME_ERROR_BASE - 3,
     ERROR_JPEGR_METADATA_ERROR          = JPEGR_RUNTIME_ERROR_BASE - 4,
+    ERROR_JPEGR_TONEMAP_ERROR           = JPEGR_RUNTIME_ERROR_BASE - 5,
 };
 
 }  // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 5597303..74f9776 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -129,6 +129,28 @@
 class RecoveryMap {
 public:
     /*
+     * Encode API-0
+     * Compress JPEGR image from 10-bit HDR YUV.
+     *
+     * Tonemap the HDR input to a SDR image, generate recovery map from the HDR and SDR images,
+     * compress SDR YUV to 8-bit JPEG and append the recovery map to the end of the compressed
+     * JPEG.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param hdr_tf transfer function of the HDR image
+     * @param dest destination of the compressed JPEGR image
+     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
+     *                the highest quality
+     * @param exif pointer to the exif metadata.
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         jpegr_transfer_function hdr_tf,
+                         jr_compressed_ptr dest,
+                         int quality,
+                         jr_exif_ptr exif);
+
+    /*
+     * Encode API-1
      * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
      *
      * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
@@ -151,6 +173,7 @@
                          jr_exif_ptr exif);
 
     /*
+     * Encode API-2
      * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG.
      *
      * This method requires HAL Hardware JPEG encoder.
@@ -159,6 +182,8 @@
      * compressed JPEG. HDR and SDR inputs must be the same resolution and color space.
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     *                                   Note: the SDR image must be the decoded version of the JPEG
+     *                                         input
      * @param compressed_jpeg_image compressed 8-bit JPEG image
      * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image
@@ -171,6 +196,7 @@
                          jr_compressed_ptr dest);
 
     /*
+     * Encode API-3
      * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
      *
      * This method requires HAL Hardware JPEG encoder.
@@ -190,6 +216,7 @@
                          jr_compressed_ptr dest);
 
     /*
+     * Decode API
      * Decompress JPEGR image.
      *
      * The output JPEGR image is in RGBA_1010102 data format if decoding to HDR.
@@ -356,6 +383,16 @@
      * @return XMP metadata in type of string
      */
     std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
+
+    /*
+     * This method will tone map a HDR image to an SDR image.
+     *
+     * @param uncompressed_p010_image (input) uncompressed P010 image
+     * @param dest (output) tone mapping result as a YUV_420 image
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t toneMap(jr_uncompressed_ptr uncompressed_p010_image,
+                     jr_uncompressed_ptr dest);
 };
 
 } // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index f7f3622..c9ac921 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -96,6 +96,59 @@
   return NO_ERROR;
 }
 
+/* Encode API-0 */
+status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                                  jpegr_transfer_function hdr_tf,
+                                  jr_compressed_ptr dest,
+                                  int quality,
+                                  jr_exif_ptr /* exif */) {
+  if (uncompressed_p010_image == nullptr || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  if (quality < 0 || quality > 100) {
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
+  jpegr_uncompressed_struct uncompressed_yuv_420_image;
+  JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
+
+  jpegr_uncompressed_struct map;
+  JPEGR_CHECK(generateRecoveryMap(
+      &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+  std::unique_ptr<uint8_t[]> map_data;
+  map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+
+  jpegr_compressed_struct compressed_map;
+  compressed_map.maxLength = map.width * map.height;
+  unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
+  compressed_map.data = compressed_map_data.get();
+  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+
+  JpegEncoder jpeg_encoder;
+  // TODO: determine ICC data based on color gamut information
+  if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
+                                  uncompressed_yuv_420_image.width,
+                                  uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
+    return ERROR_JPEGR_ENCODE_ERROR;
+  }
+  jpegr_compressed_struct jpeg;
+  jpeg.data = jpeg_encoder.getCompressedImagePtr();
+  jpeg.length = jpeg_encoder.getCompressedImageSize();
+
+  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
+
+  return NO_ERROR;
+}
+
+/* Encode API-1 */
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_uncompressed_ptr uncompressed_yuv_420_image,
                                   jpegr_transfer_function hdr_tf,
@@ -152,6 +205,7 @@
   return NO_ERROR;
 }
 
+/* Encode API-2 */
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_uncompressed_ptr uncompressed_yuv_420_image,
                                   jr_compressed_ptr compressed_jpeg_image,
@@ -193,6 +247,7 @@
   return NO_ERROR;
 }
 
+/* Encode API-3 */
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_compressed_ptr compressed_jpeg_image,
                                   jpegr_transfer_function hdr_tf,
@@ -262,7 +317,7 @@
   return NO_ERROR;
 }
 
-
+/* Decode API */
 status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
                                   jr_uncompressed_ptr dest,
                                   jr_exif_ptr exif,
@@ -676,4 +731,20 @@
   return ss.str();
 }
 
+status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
+                              jr_uncompressed_ptr dest) {
+  if (uncompressed_p010_image == nullptr || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  dest->width = uncompressed_p010_image->width;
+  dest->height = uncompressed_p010_image->height;
+  unique_ptr<uint8_t[]> dest_data = make_unique<uint8_t[]>(dest->width * dest->height * 3 / 2);
+  dest->data = dest_data.get();
+
+  // TODO: Tone map algorighm here.
+
+  return NO_ERROR;
+}
+
 } // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index ade33a0..0f96723 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -85,6 +85,7 @@
 TEST_F(RecoveryMapTest, build) {
   // Force all of the recovery map lib to be linked by calling all public functions.
   RecoveryMap recovery_map;
+  recovery_map.encodeJPEGR(nullptr, static_cast<jpegr_transfer_function>(0), nullptr, 0, nullptr);
   recovery_map.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
                            nullptr, 0, nullptr);
   recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
@@ -93,6 +94,58 @@
   recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false);
 }
 
+TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
+  int ret;
+
+  // Load input files.
+  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  }
+  mRawP010Image.width = RAW_P010_IMAGE_WIDTH;
+  mRawP010Image.height = RAW_P010_IMAGE_HEIGHT;
+  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+  RecoveryMap recoveryMap;
+
+  jpegr_compressed_struct jpegR;
+  jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t);
+  jpegR.data = malloc(jpegR.maxLength);
+  ret = recoveryMap.encodeJPEGR(
+      &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, 90, nullptr);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_ENCODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)jpegR.data, jpegR.length);
+  }
+
+  jpegr_uncompressed_struct decodedJpegR;
+  int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4;
+  decodedJpegR.data = malloc(decodedJpegRSize);
+  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_DECODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+  }
+
+  free(jpegR.data);
+  free(decodedJpegR.data);
+}
+
 TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) {
   int ret;
 
diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp
index 60328e4..bf0805b 100644
--- a/libs/nativedisplay/ADisplay.cpp
+++ b/libs/nativedisplay/ADisplay.cpp
@@ -117,15 +117,6 @@
 #define CHECK_NOT_NULL(name) \
     LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument");
 
-namespace {
-
-sp<IBinder> getToken(ADisplay* display) {
-    DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
-    return SurfaceComposerClient::getPhysicalDisplayToken(impl->id);
-}
-
-} // namespace
-
 namespace android {
 
 int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) {
@@ -139,10 +130,9 @@
     ui::DisplayConnectionType displayConnectionTypes[size];
     int numModes = 0;
     for (int i = 0; i < size; ++i) {
-        const sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(ids[i]);
-
         ui::StaticDisplayInfo staticInfo;
-        if (const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &staticInfo);
+        if (const status_t status =
+                    SurfaceComposerClient::getStaticDisplayInfo(ids[i].value, &staticInfo);
             status != OK) {
             return status;
         }
@@ -150,7 +140,7 @@
 
         ui::DynamicDisplayInfo dynamicInfo;
         if (const status_t status =
-                    SurfaceComposerClient::getDynamicDisplayInfo(token, &dynamicInfo);
+                    SurfaceComposerClient::getDynamicDisplayInfoFromId(ids[i].value, &dynamicInfo);
             status != OK) {
             return status;
         }
@@ -260,14 +250,15 @@
 int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig) {
     CHECK_NOT_NULL(display);
 
-    sp<IBinder> token = getToken(display);
     ui::DynamicDisplayInfo info;
-    if (const auto status = SurfaceComposerClient::getDynamicDisplayInfo(token, &info);
+    DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+
+    if (const auto status =
+                SurfaceComposerClient::getDynamicDisplayInfoFromId(impl->id.value, &info);
         status != OK) {
         return status;
     }
 
-    DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
     for (size_t i = 0; i < impl->numConfigs; i++) {
         auto* config = impl->configs + i;
         if (config->id == info.activeDisplayModeId) {
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 973986f..b885435 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -70,7 +70,7 @@
     name: "libinputflinger_defaults",
     srcs: [":libinputflinger_sources"],
     shared_libs: [
-        "android.hardware.input.processor-V2-ndk",
+        "android.hardware.input.processor-V1-ndk",
         "libbase",
         "libbinder",
         "libbinder_ndk",
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index b292c09..628ce6f 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -263,10 +263,7 @@
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14);
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15);
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16);
-static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET) ==
-              common::Axis::GESTURE_X_OFFSET);
-static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET) ==
-              common::Axis::GESTURE_Y_OFFSET);
+// TODO(hcutts): add GESTURE_X_OFFSET and GESTURE_Y_OFFSET.
 // If you added a new axis, consider whether this should also be exposed as a HAL axis. Update the
 // static_assert below and add the new axis here, or leave a comment summarizing your decision.
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) ==
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index a34cb4c..f37f0fa 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -74,6 +74,7 @@
         "libcutils",
         "libjsoncpp",
         "liblog",
+        "libPlatformProperties",
         "libstatslog",
         "libutils",
     ],
@@ -90,7 +91,6 @@
     target: {
         android: {
             shared_libs: [
-                "libPlatformProperties",
                 "libinput",
             ],
         },
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index f9d72e0..11b5209 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -20,9 +20,7 @@
 
 #include <algorithm>
 
-#if defined(__ANDROID__)
 #include <android/sysprop/InputProperties.sysprop.h>
-#endif
 #include <ftl/flags.h>
 
 #include "CursorInputMapper.h"
@@ -213,11 +211,7 @@
 
     // Touchscreens and touchpad devices.
     static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
-#if defined(__ANDROID__)
             sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false);
-#else
-            false;
-#endif
     if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
         classes.test(InputDeviceClass::TOUCH_MT)) {
         mappers.push_back(std::make_unique<TouchpadInputMapper>(*contextPtr));
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index b193dff..633efc6 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -16,10 +16,8 @@
 
 #include "../Macros.h"
 
-#include "MultiTouchInputMapper.h"
-#if defined(__ANDROID__)
 #include <android/sysprop/InputProperties.sysprop.h>
-#endif
+#include "MultiTouchInputMapper.h"
 
 namespace android {
 
@@ -218,12 +216,7 @@
 
 bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const {
     static const bool SIMULATE_STYLUS_WITH_TOUCH =
-#if defined(__ANDROID__)
             sysprop::InputProperties::simulate_stylus_with_touch().value_or(false);
-#else
-            // Disable this developer feature where sysproperties are not available
-            false;
-#endif
     return SIMULATE_STYLUS_WITH_TOUCH &&
             mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
 }
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 4359a4b..55c2db6 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -55,7 +55,7 @@
     ],
     shared_libs: [
         "android.hardware.input.classifier@1.0",
-        "android.hardware.input.processor-V2-ndk",
+        "android.hardware.input.processor-V1-ndk",
         "libbase",
         "libbinder",
         "libcutils",
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index b1bd705..0a192c5 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -157,6 +157,7 @@
         "EventLog/EventLog.cpp",
         "FrontEnd/LayerCreationArgs.cpp",
         "FrontEnd/LayerHandle.cpp",
+        "FrontEnd/LayerHierarchy.cpp",
         "FrontEnd/LayerLifecycleManager.cpp",
         "FrontEnd/RequestedLayerState.cpp",
         "FrontEnd/TransactionHandler.cpp",
@@ -165,7 +166,6 @@
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
         "HdrLayerInfoReporter.cpp",
-        "HwcSlotGenerator.cpp",
         "WindowInfosListenerInvoker.cpp",
         "Layer.cpp",
         "LayerFE.cpp",
diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp
index 2bd8f32..09e41ff 100644
--- a/services/surfaceflinger/ClientCache.cpp
+++ b/services/surfaceflinger/ClientCache.cpp
@@ -118,7 +118,8 @@
                                                                  Usage::READABLE));
 }
 
-void ClientCache::erase(const client_cache_t& cacheId) {
+sp<GraphicBuffer> ClientCache::erase(const client_cache_t& cacheId) {
+    sp<GraphicBuffer> buffer;
     auto& [processToken, id] = cacheId;
     std::vector<sp<ErasedRecipient>> pendingErase;
     {
@@ -126,9 +127,11 @@
         ClientCacheBuffer* buf = nullptr;
         if (!getBuffer(cacheId, &buf)) {
             ALOGE("failed to erase buffer, could not retrieve buffer");
-            return;
+            return nullptr;
         }
 
+        buffer = buf->buffer->getBuffer();
+
         for (auto& recipient : buf->recipients) {
             sp<ErasedRecipient> erasedRecipient = recipient.promote();
             if (erasedRecipient) {
@@ -142,6 +145,7 @@
     for (auto& recipient : pendingErase) {
         recipient->bufferErased(cacheId);
     }
+    return buffer;
 }
 
 std::shared_ptr<renderengine::ExternalTexture> ClientCache::get(const client_cache_t& cacheId) {
diff --git a/services/surfaceflinger/ClientCache.h b/services/surfaceflinger/ClientCache.h
index cdeac2b..b56b252 100644
--- a/services/surfaceflinger/ClientCache.h
+++ b/services/surfaceflinger/ClientCache.h
@@ -33,6 +33,17 @@
 
 namespace android {
 
+// This class manages a cache of buffer handles between SurfaceFlinger clients
+// and the SurfaceFlinger process which optimizes away some of the cost of
+// sending buffer handles across processes.
+//
+// Buffers are explicitly cached and uncached by the SurfaceFlinger client. When
+// a buffer is uncached, it is not only purged from this cache, but the buffer
+// ID is also passed down to CompositionEngine to purge it from a similar cache
+// used between SurfaceFlinger and Composer HAL. The buffer ID used to purge
+// both the SurfaceFlinger side of this other cache, as well as Composer HAL's
+// side of the cache.
+//
 class ClientCache : public Singleton<ClientCache> {
 public:
     ClientCache();
@@ -41,7 +52,8 @@
 
     base::expected<std::shared_ptr<renderengine::ExternalTexture>, AddError> add(
             const client_cache_t& cacheId, const sp<GraphicBuffer>& buffer);
-    void erase(const client_cache_t& cacheId);
+
+    sp<GraphicBuffer> erase(const client_cache_t& cacheId);
 
     std::shared_ptr<renderengine::ExternalTexture> get(const client_cache_t& cacheId);
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index f861fc9..415a041 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -52,6 +52,9 @@
     // All the layers that have queued updates.
     Layers layersWithQueuedFrames;
 
+    // All graphic buffers that will no longer be used and should be removed from caches.
+    std::vector<uint64_t> bufferIdsToUncache;
+
     // Controls how the color mode is chosen for an output
     OutputColorSetting outputColorSetting{OutputColorSetting::kEnhanced};
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 974f7c6..ad98e93 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -163,7 +163,6 @@
 
     // The buffer and related state
     sp<GraphicBuffer> buffer;
-    int bufferSlot{BufferQueue::INVALID_BUFFER_SLOT};
     sp<Fence> acquireFence = Fence::NO_FENCE;
     Region surfaceDamage;
     uint64_t frameNumber = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 874b330..bd43c89 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -23,6 +23,7 @@
 #include <type_traits>
 #include <unordered_map>
 #include <utility>
+#include <vector>
 
 #include <compositionengine/LayerFE.h>
 #include <renderengine/LayerSettings.h>
@@ -272,6 +273,7 @@
     virtual void setDisplayColorProfile(std::unique_ptr<DisplayColorProfile>) = 0;
     virtual void setRenderSurface(std::unique_ptr<RenderSurface>) = 0;
 
+    virtual void uncacheBuffers(const std::vector<uint64_t>&) = 0;
     virtual void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) = 0;
     virtual void collectVisibleLayers(const CompositionRefreshArgs&, CoverageState&) = 0;
     virtual void ensureOutputLayerIfVisible(sp<LayerFE>&, CoverageState&) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index 6d0c395..4dbf8d2 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -19,6 +19,7 @@
 #include <cstdint>
 #include <optional>
 #include <string>
+#include <vector>
 
 #include <ui/Transform.h>
 #include <utils/StrongPointer.h>
@@ -81,6 +82,10 @@
     // TODO(lpique): Make this protected once it is only internally called.
     virtual CompositionState& editState() = 0;
 
+    // Clear the cache entries for a set of buffers that SurfaceFlinger no
+    // longer cares about.
+    virtual void uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) = 0;
+
     // Recalculates the state of the output layer from the output-independent
     // layer. If includeGeometry is false, the geometry state can be skipped.
     // internalDisplayRotationFlags must be set to the rotation flags for the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
index fd22aa3..6e9ea6f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
@@ -17,7 +17,8 @@
 #pragma once
 
 #include <cstdint>
-#include <vector>
+#include <stack>
+#include <unordered_map>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -37,35 +38,76 @@
 
 namespace compositionengine::impl {
 
-// With HIDLized hwcomposer HAL, the HAL can maintain a buffer cache for each
-// HWC display and layer.  When updating a display target or a layer buffer,
-// we have the option to send the buffer handle over or to request the HAL to
-// retrieve it from its cache.  The latter is cheaper since it eliminates the
-// overhead to transfer the handle over the trasport layer, and the overhead
-// for the HAL to clone and retain the handle.
-//
-// To be able to find out whether a buffer is already in the HAL's cache, we
-// use HWComposerBufferCache to mirror the cache in SF.
-class HwcBufferCache {
-public:
-    HwcBufferCache();
-    // Given a buffer, return the HWC cache slot and
-    // buffer to be sent to HWC.
-    //
-    // outBuffer is set to buffer when buffer is not in the HWC cache;
-    // otherwise, outBuffer is set to nullptr.
-    void getHwcBuffer(int slot, const sp<GraphicBuffer>& buffer, uint32_t* outSlot,
-                      sp<GraphicBuffer>* outBuffer);
+// The buffer cache returns both a slot and the buffer that should be sent to HWC. In cases
+// where the buffer is already cached, the buffer is a nullptr and will not be sent to HWC as
+// an optimization.
+struct HwcSlotAndBuffer {
+    uint32_t slot;
+    sp<GraphicBuffer> buffer;
+};
 
-    // Special caching slot for the layer caching feature.
-    static const constexpr size_t FLATTENER_CACHING_SLOT = BufferQueue::NUM_BUFFER_SLOTS;
+//
+// Manages the slot assignments for a buffers stored in Composer HAL's cache.
+//
+// Cache slots are an optimization when communicating buffer handles to Composer
+// HAL. When updating a layer's buffer, we can either send a new buffer handle
+// along with it's slot assignment or request the HAL to reuse a buffer handle
+// that we've already sent by using the slot assignment. The latter is cheaper
+// since it eliminates the overhead to transfer the buffer handle over IPC and
+// the overhead for the HAL to clone the handle.
+//
+class HwcBufferCache {
+private:
+    static const constexpr size_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS;
+
+public:
+    // public for testing
+    // Override buffers don't use the normal cache slots because we don't want them to evict client
+    // buffers from the cache. We add an extra slot at the end for the override buffers.
+    static const constexpr size_t kOverrideBufferSlot = kMaxLayerBufferCount;
+
+    HwcBufferCache();
+
+    //
+    // Given a buffer, return the HWC cache slot and buffer to send to HWC.
+    //
+    // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the
+    // buffer handle.
+    //
+    HwcSlotAndBuffer getHwcSlotAndBuffer(const sp<GraphicBuffer>& buffer);
+    //
+    // Given a buffer, return the HWC cache slot and buffer to send to HWC.
+    //
+    // A special slot number is used for override buffers.
+    //
+    // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the
+    // buffer handle.
+    //
+    HwcSlotAndBuffer getHwcSlotAndBufferForOverride(const sp<GraphicBuffer>& buffer);
+
+    //
+    // When a client process discards a buffer, it needs to be purged from the HWC cache.
+    //
+    // Returns the slot number of the buffer, or UINT32_MAX if it wasn't found in the cache.
+    //
+    uint32_t uncache(uint64_t graphicBufferId);
 
 private:
-    // an array where the index corresponds to a slot and the value corresponds to a (counter,
-    // buffer) pair. "counter" is a unique value that indicates the last time this slot was updated
-    // or used and allows us to keep track of the least-recently used buffer.
-    static const constexpr size_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS + 1;
-    wp<GraphicBuffer> mBuffers[kMaxLayerBufferCount];
+    uint32_t cache(const sp<GraphicBuffer>& buffer);
+    uint32_t getLeastRecentlyUsedSlot();
+
+    struct Cache {
+        sp<GraphicBuffer> buffer;
+        uint32_t slot;
+        // Cache entries are evicted according to least-recently-used when more than
+        // kMaxLayerBufferCount unique buffers have been sent to a layer.
+        uint64_t lruCounter;
+    };
+
+    std::unordered_map<uint64_t, Cache> mCacheByBufferId;
+    sp<GraphicBuffer> mLastOverrideBuffer;
+    std::stack<uint32_t> mFreeSlots;
+    uint64_t mLeastRecentlyUsedCounter;
 };
 
 } // namespace compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 9ca5da9..1393e29 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -82,6 +82,7 @@
     void prepare(const CompositionRefreshArgs&, LayerFESet&) override;
     void present(const CompositionRefreshArgs&) override;
 
+    void uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) override;
     void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) override;
     void collectVisibleLayers(const CompositionRefreshArgs&,
                               compositionengine::Output::CoverageState&) override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 6d4abf9..f383392 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <vector>
 
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/OutputLayer.h>
@@ -44,6 +45,8 @@
 
     void setHwcLayer(std::shared_ptr<HWC2::Layer>) override;
 
+    void uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) override;
+
     void updateCompositionState(bool includeGeometry, bool forceClientComposition,
                                 ui::Transform::RotationFlags) override;
     void writeStateToHWC(bool includeGeometry, bool skipLayer, uint32_t z, bool zIsOverridden,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 7592cac..18e6879 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -82,6 +82,7 @@
     MOCK_METHOD2(prepare, void(const compositionengine::CompositionRefreshArgs&, LayerFESet&));
     MOCK_METHOD1(present, void(const compositionengine::CompositionRefreshArgs&));
 
+    MOCK_METHOD1(uncacheBuffers, void(const std::vector<uint64_t>&));
     MOCK_METHOD2(rebuildLayerStacks,
                  void(const compositionengine::CompositionRefreshArgs&, LayerFESet&));
     MOCK_METHOD2(collectVisibleLayers,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index c22f1bf..5fef63a 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -35,6 +35,8 @@
 
     MOCK_METHOD1(setHwcLayer, void(std::shared_ptr<HWC2::Layer>));
 
+    MOCK_METHOD1(uncacheBuffers, void(const std::vector<uint64_t>&));
+
     MOCK_CONST_METHOD0(getOutput, const compositionengine::Output&());
     MOCK_CONST_METHOD0(getLayerFE, compositionengine::LayerFE&());
 
diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
index f95382d..d64fd57 100644
--- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
@@ -16,43 +16,80 @@
 
 #include <compositionengine/impl/HwcBufferCache.h>
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <gui/BufferQueue.h>
 #include <ui/GraphicBuffer.h>
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
-
 namespace android::compositionengine::impl {
 
 HwcBufferCache::HwcBufferCache() {
-    std::fill(std::begin(mBuffers), std::end(mBuffers), wp<GraphicBuffer>(nullptr));
+    for (uint32_t i = 0; i < kMaxLayerBufferCount; i++) {
+        mFreeSlots.push(i);
+    }
 }
 
-void HwcBufferCache::getHwcBuffer(int slot, const sp<GraphicBuffer>& buffer, uint32_t* outSlot,
-                                  sp<GraphicBuffer>* outBuffer) {
-    // default is 0
-    if (slot == BufferQueue::INVALID_BUFFER_SLOT || slot < 0 ||
-        slot >= static_cast<int32_t>(kMaxLayerBufferCount)) {
-        *outSlot = 0;
-    } else {
-        *outSlot = static_cast<uint32_t>(slot);
+HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBuffer(const sp<GraphicBuffer>& buffer) {
+    // TODO(b/261930578): This is for unit tests which don't mock GraphicBuffers but instead send
+    // in nullptrs.
+    if (buffer == nullptr) {
+        return {0, nullptr};
     }
-
-    auto& currentBuffer = mBuffers[*outSlot];
-    wp<GraphicBuffer> weakCopy(buffer);
-    if (currentBuffer == weakCopy) {
-        // already cached in HWC, skip sending the buffer
-        *outBuffer = nullptr;
-    } else {
-        *outBuffer = buffer;
-
-        // update cache
-        currentBuffer = buffer;
+    if (auto i = mCacheByBufferId.find(buffer->getId()); i != mCacheByBufferId.end()) {
+        Cache& cache = i->second;
+        // mark this cache slot as more recently used so it won't get evicted anytime soon
+        cache.lruCounter = mLeastRecentlyUsedCounter++;
+        return {cache.slot, nullptr};
     }
+    return {cache(buffer), buffer};
+}
+
+HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBufferForOverride(const sp<GraphicBuffer>& buffer) {
+    if (buffer == mLastOverrideBuffer) {
+        return {kOverrideBufferSlot, nullptr};
+    }
+    mLastOverrideBuffer = buffer;
+    return {kOverrideBufferSlot, buffer};
+}
+
+uint32_t HwcBufferCache::uncache(uint64_t bufferId) {
+    if (auto i = mCacheByBufferId.find(bufferId); i != mCacheByBufferId.end()) {
+        uint32_t slot = i->second.slot;
+        mCacheByBufferId.erase(i);
+        mFreeSlots.push(slot);
+        return slot;
+    }
+    if (mLastOverrideBuffer && bufferId == mLastOverrideBuffer->getId()) {
+        mLastOverrideBuffer = nullptr;
+        return kOverrideBufferSlot;
+    }
+    return UINT32_MAX;
+}
+
+uint32_t HwcBufferCache::cache(const sp<GraphicBuffer>& buffer) {
+    Cache cache;
+    cache.slot = getLeastRecentlyUsedSlot();
+    cache.lruCounter = mLeastRecentlyUsedCounter++;
+    cache.buffer = buffer;
+    mCacheByBufferId.emplace(buffer->getId(), cache);
+    return cache.slot;
+}
+
+uint32_t HwcBufferCache::getLeastRecentlyUsedSlot() {
+    if (mFreeSlots.empty()) {
+        assert(!mCacheByBufferId.empty());
+        // evict the least recently used cache entry
+        auto cacheToErase = mCacheByBufferId.begin();
+        for (auto i = cacheToErase; i != mCacheByBufferId.end(); ++i) {
+            if (i->second.lruCounter < cacheToErase->second.lruCounter) {
+                cacheToErase = i;
+            }
+        }
+        uint32_t slot = cacheToErase->second.slot;
+        mCacheByBufferId.erase(cacheToErase);
+        mFreeSlots.push(slot);
+    }
+    uint32_t slot = mFreeSlots.top();
+    mFreeSlots.pop();
+    return slot;
 }
 
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index 6631a27..a405c4d 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -106,7 +106,6 @@
     dumpVal(out, "composition type", toString(compositionType), compositionType);
 
     out.append("\n      buffer: ");
-    dumpVal(out, "slot", bufferSlot);
     dumpVal(out, "buffer", buffer.get());
 
     out.append("\n      ");
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 3ee8017..16ef812 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -428,6 +428,7 @@
     ALOGV(__FUNCTION__);
 
     rebuildLayerStacks(refreshArgs, geomSnapshots);
+    uncacheBuffers(refreshArgs.bufferIdsToUncache);
 }
 
 void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
@@ -455,6 +456,15 @@
     renderCachedSets(refreshArgs);
 }
 
+void Output::uncacheBuffers(std::vector<uint64_t> const& bufferIdsToUncache) {
+    if (bufferIdsToUncache.empty()) {
+        return;
+    }
+    for (auto outputLayer : getOutputLayersOrderedByZ()) {
+        outputLayer->uncacheBuffers(bufferIdsToUncache);
+    }
+}
+
 void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs,
                                 LayerFESet& layerFESet) {
     ATRACE_CALL();
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index a39c527..a7c24b6 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -610,6 +610,19 @@
     }
 }
 
+void OutputLayer::uncacheBuffers(std::vector<uint64_t> const& bufferIdsToUncache) {
+    auto& state = editState();
+    // Skip doing this if there is no HWC interface
+    if (!state.hwc) {
+        return;
+    }
+
+    for (auto bufferId : bufferIdsToUncache) {
+        state.hwc->hwcBufferCache.uncache(bufferId);
+        // TODO(b/258196272): send uncache requests to Composer HAL
+    }
+}
+
 void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer,
                                         const LayerFECompositionState& outputIndependentState,
                                         bool skipLayer) {
@@ -622,27 +635,24 @@
               to_string(error).c_str(), static_cast<int32_t>(error));
     }
 
-    sp<GraphicBuffer> buffer = outputIndependentState.buffer;
-    sp<Fence> acquireFence = outputIndependentState.acquireFence;
-    int slot = outputIndependentState.bufferSlot;
+    HwcSlotAndBuffer hwcSlotAndBuffer;
+    sp<Fence> hwcFence;
+    // Override buffers use a special cache slot so that they don't evict client buffers.
     if (getState().overrideInfo.buffer != nullptr && !skipLayer) {
-        buffer = getState().overrideInfo.buffer->getBuffer();
-        acquireFence = getState().overrideInfo.acquireFence;
-        slot = HwcBufferCache::FLATTENER_CACHING_SLOT;
+        hwcSlotAndBuffer = editState().hwc->hwcBufferCache.getHwcSlotAndBufferForOverride(
+                getState().overrideInfo.buffer->getBuffer());
+        hwcFence = getState().overrideInfo.acquireFence;
+    } else {
+        hwcSlotAndBuffer =
+                editState().hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer);
+        hwcFence = outputIndependentState.acquireFence;
     }
 
-    ALOGV("Writing buffer %p", buffer.get());
-
-    uint32_t hwcSlot = 0;
-    sp<GraphicBuffer> hwcBuffer;
-    // We need access to the output-dependent state for the buffer cache there,
-    // though otherwise the buffer is not output-dependent.
-    editState().hwc->hwcBufferCache.getHwcBuffer(slot, buffer, &hwcSlot, &hwcBuffer);
-
-    if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, acquireFence);
+    if (auto error = hwcLayer->setBuffer(hwcSlotAndBuffer.slot, hwcSlotAndBuffer.buffer, hwcFence);
         error != hal::Error::NONE) {
-        ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), buffer->handle,
-              to_string(error).c_str(), static_cast<int32_t>(error));
+        ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(),
+              hwcSlotAndBuffer.buffer->handle, to_string(error).c_str(),
+              static_cast<int32_t>(error));
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
index 7197780..cf72e8f 100644
--- a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
@@ -22,66 +22,172 @@
 namespace android::compositionengine {
 namespace {
 
-class TestableHwcBufferCache : public impl::HwcBufferCache {
-public:
-    void getHwcBuffer(int slot, const sp<GraphicBuffer>& buffer, uint32_t* outSlot,
-                      sp<GraphicBuffer>* outBuffer) {
-        HwcBufferCache::getHwcBuffer(slot, buffer, outSlot, outBuffer);
-    }
-};
+using impl::HwcBufferCache;
+using impl::HwcSlotAndBuffer;
 
 class HwcBufferCacheTest : public testing::Test {
 public:
     ~HwcBufferCacheTest() override = default;
 
-    void testSlot(const int inSlot, const uint32_t expectedSlot) {
-        uint32_t outSlot;
-        sp<GraphicBuffer> outBuffer;
-
-        // The first time, the output  is the same as the input
-        mCache.getHwcBuffer(inSlot, mBuffer1, &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(mBuffer1, outBuffer);
-
-        // The second time with the same buffer, the outBuffer is nullptr.
-        mCache.getHwcBuffer(inSlot, mBuffer1, &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(nullptr, outBuffer.get());
-
-        // With a new buffer, the outBuffer is the input.
-        mCache.getHwcBuffer(inSlot, mBuffer2, &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(mBuffer2, outBuffer);
-
-        // Again, the second request with the same buffer sets outBuffer to nullptr.
-        mCache.getHwcBuffer(inSlot, mBuffer2, &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(nullptr, outBuffer.get());
-
-        // Setting a slot to use nullptr lookslike works, but note that
-        // the output values make it look like no new buffer is being set....
-        mCache.getHwcBuffer(inSlot, sp<GraphicBuffer>(), &outSlot, &outBuffer);
-        EXPECT_EQ(expectedSlot, outSlot);
-        EXPECT_EQ(nullptr, outBuffer.get());
-    }
-
-    impl::HwcBufferCache mCache;
     sp<GraphicBuffer> mBuffer1 =
             sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
     sp<GraphicBuffer> mBuffer2 =
             sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
 };
 
-TEST_F(HwcBufferCacheTest, cacheWorksForSlotZero) {
-    testSlot(0, 0);
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_returnsUniqueSlotNumberForEachBuffer) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1);
+    EXPECT_NE(slotAndBufferFor1.slot, UINT32_MAX);
+    EXPECT_EQ(slotAndBufferFor1.buffer, mBuffer1);
+
+    HwcSlotAndBuffer slotAndBufferFor2 = cache.getHwcSlotAndBuffer(mBuffer2);
+    EXPECT_NE(slotAndBufferFor2.slot, slotAndBufferFor1.slot);
+    EXPECT_NE(slotAndBufferFor2.slot, UINT32_MAX);
+    EXPECT_EQ(slotAndBufferFor2.buffer, mBuffer2);
 }
 
-TEST_F(HwcBufferCacheTest, cacheWorksForMaxSlot) {
-    testSlot(BufferQueue::NUM_BUFFER_SLOTS - 1, BufferQueue::NUM_BUFFER_SLOTS - 1);
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_whenCached_returnsSameSlotNumberAndNullBuffer) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBuffer(mBuffer1);
+    EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX);
+    EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1);
+
+    HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBuffer(mBuffer1);
+    EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot);
+    EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr);
 }
 
-TEST_F(HwcBufferCacheTest, cacheMapsNegativeSlotToZero) {
-    testSlot(-123, 0);
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_whenSlotsFull_evictsOldestCachedBuffer) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    sp<GraphicBuffer> graphicBuffers[100];
+    HwcSlotAndBuffer slotsAndBuffers[100];
+    int finalCachedBufferIndex = 0;
+    for (int i = 0; i < 100; ++i) {
+        graphicBuffers[i] = sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
+        slotsAndBuffers[i] = cache.getHwcSlotAndBuffer(graphicBuffers[i]);
+        // we fill up the cache when the slot number for the first buffer is reused
+        if (i > 0 && slotsAndBuffers[i].slot == slotsAndBuffers[0].slot) {
+            finalCachedBufferIndex = i;
+            break;
+        }
+    }
+    ASSERT_GT(finalCachedBufferIndex, 1);
+    // the final cached buffer has the same slot value as the oldest buffer
+    EXPECT_EQ(slotsAndBuffers[finalCachedBufferIndex].slot, slotsAndBuffers[0].slot);
+    // the oldest buffer is no longer in the cache because it was evicted
+    EXPECT_EQ(cache.uncache(graphicBuffers[0]->getId()), UINT32_MAX);
+}
+
+TEST_F(HwcBufferCacheTest, uncache_whenCached_returnsSlotNumber) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1);
+    ASSERT_NE(slotAndBufferFor1.slot, UINT32_MAX);
+
+    HwcSlotAndBuffer slotAndBufferFor2 = cache.getHwcSlotAndBuffer(mBuffer2);
+    ASSERT_NE(slotAndBufferFor2.slot, UINT32_MAX);
+
+    // the 1st buffer should be found in the cache with a slot number
+    EXPECT_EQ(cache.uncache(mBuffer1->getId()), slotAndBufferFor1.slot);
+    // since the 1st buffer has been previously uncached, we should no longer receive a slot number
+    EXPECT_EQ(cache.uncache(mBuffer1->getId()), UINT32_MAX);
+    // the 2nd buffer should be still found in the cache with a slot number
+    EXPECT_EQ(cache.uncache(mBuffer2->getId()), slotAndBufferFor2.slot);
+    // since the 2nd buffer has been previously uncached, we should no longer receive a slot number
+    EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
+}
+
+TEST_F(HwcBufferCacheTest, uncache_whenUncached_returnsInvalidSlotNumber) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1);
+    ASSERT_NE(slotAndBufferFor1.slot, UINT32_MAX);
+
+    EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
+}
+
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenCached_returnsSameSlotAndNullBuffer) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX);
+    EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1);
+
+    HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot);
+    EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr);
+}
+
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenSlotsFull_returnsIndependentSlot) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    sp<GraphicBuffer> graphicBuffers[100];
+    HwcSlotAndBuffer slotsAndBuffers[100];
+    int finalCachedBufferIndex = -1;
+    for (int i = 0; i < 100; ++i) {
+        graphicBuffers[i] = sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
+        slotsAndBuffers[i] = cache.getHwcSlotAndBuffer(graphicBuffers[i]);
+        // we fill up the cache when the slot number for the first buffer is reused
+        if (i > 0 && slotsAndBuffers[i].slot == slotsAndBuffers[0].slot) {
+            finalCachedBufferIndex = i;
+            break;
+        }
+    }
+    // expect to have cached at least a few buffers before evicting
+    ASSERT_GT(finalCachedBufferIndex, 1);
+
+    sp<GraphicBuffer> overrideBuffer =
+            sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
+    HwcSlotAndBuffer overrideSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(overrideBuffer);
+    // expect us to have a slot number
+    EXPECT_NE(overrideSlotAndBuffer.slot, UINT32_MAX);
+    // expect this to be the first time we cached the buffer
+    EXPECT_NE(overrideSlotAndBuffer.buffer, nullptr);
+
+    // expect the slot number to not equal any other slot number, even after the slots have been
+    // exhausted, indicating that the override buffer slot is independent from the slots for
+    // non-override buffers
+    for (int i = 0; i < finalCachedBufferIndex; ++i) {
+        EXPECT_NE(overrideSlotAndBuffer.slot, slotsAndBuffers[i].slot);
+    }
+    // the override buffer is independently uncached from the oldest cached buffer
+    // expect to find the override buffer still in the override buffer slot
+    EXPECT_EQ(cache.uncache(overrideBuffer->getId()), overrideSlotAndBuffer.slot);
+    // expect that the first buffer was not evicted from the cache when the override buffer was
+    // cached
+    EXPECT_EQ(cache.uncache(graphicBuffers[1]->getId()), slotsAndBuffers[1].slot);
+}
+
+TEST_F(HwcBufferCacheTest, uncache_whenOverrideCached_returnsSlotNumber) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX);
+
+    EXPECT_EQ(cache.uncache(mBuffer1->getId()), hwcSlotAndBuffer.slot);
+    EXPECT_EQ(cache.uncache(mBuffer1->getId()), UINT32_MAX);
+}
+
+TEST_F(HwcBufferCacheTest, uncache_whenOverrideUncached_returnsInvalidSlotNumber) {
+    HwcBufferCache cache;
+    sp<GraphicBuffer> outBuffer;
+
+    HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX);
+
+    EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 0f7dce8..03cf292 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -774,7 +774,7 @@
     static constexpr ui::Dataspace kOverrideDataspace = static_cast<ui::Dataspace>(72);
     static constexpr int kSupportedPerFrameMetadata = 101;
     static constexpr int kExpectedHwcSlot = 0;
-    static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::FLATTENER_CACHING_SLOT;
+    static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::kOverrideBufferSlot;
     static constexpr bool kLayerGenericMetadata1Mandatory = true;
     static constexpr bool kLayerGenericMetadata2Mandatory = true;
     static constexpr float kWhitePointNits = 200.f;
@@ -823,7 +823,6 @@
         mLayerFEState.hdrMetadata = kHdrMetadata;
         mLayerFEState.sidebandStream = NativeHandle::create(kSidebandStreamHandle, false);
         mLayerFEState.buffer = kBuffer;
-        mLayerFEState.bufferSlot = BufferQueue::INVALID_BUFFER_SLOT;
         mLayerFEState.acquireFence = kFence;
 
         mOutputState.displayBrightnessNits = kDisplayBrightnessNits;
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 2109987..bfd863b 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -1192,14 +1192,49 @@
                           compositionengine::LayerFESet&));
     };
 
+    OutputPrepareTest() {
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0))
+                .WillRepeatedly(Return(&mLayer1.outputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1))
+                .WillRepeatedly(Return(&mLayer2.outputLayer));
+
+        mRefreshArgs.layers.push_back(mLayer1.layerFE);
+        mRefreshArgs.layers.push_back(mLayer2.layerFE);
+    }
+
+    struct Layer {
+        StrictMock<mock::OutputLayer> outputLayer;
+        sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
+    };
+
     StrictMock<OutputPartialMock> mOutput;
     CompositionRefreshArgs mRefreshArgs;
     LayerFESet mGeomSnapshots;
+    Layer mLayer1;
+    Layer mLayer2;
 };
 
-TEST_F(OutputPrepareTest, justInvokesRebuildLayerStacks) {
+TEST_F(OutputPrepareTest, callsUncacheBuffersOnEachOutputLayerAndThenRebuildsLayerStacks) {
     InSequence seq;
+
+    mRefreshArgs.bufferIdsToUncache = {1, 3, 5};
+
     EXPECT_CALL(mOutput, rebuildLayerStacks(Ref(mRefreshArgs), Ref(mGeomSnapshots)));
+    EXPECT_CALL(mLayer1.outputLayer, uncacheBuffers(Ref(mRefreshArgs.bufferIdsToUncache)));
+    EXPECT_CALL(mLayer2.outputLayer, uncacheBuffers(Ref(mRefreshArgs.bufferIdsToUncache)));
+
+    mOutput.prepare(mRefreshArgs, mGeomSnapshots);
+}
+
+TEST_F(OutputPrepareTest, skipsUncacheBuffersIfEmptyAndThenRebuildsLayerStacks) {
+    InSequence seq;
+
+    mRefreshArgs.bufferIdsToUncache = {};
+
+    EXPECT_CALL(mOutput, rebuildLayerStacks(Ref(mRefreshArgs), Ref(mGeomSnapshots)));
+    EXPECT_CALL(mLayer1.outputLayer, uncacheBuffers(_)).Times(0);
+    EXPECT_CALL(mLayer2.outputLayer, uncacheBuffers(_)).Times(0);
 
     mOutput.prepare(mRefreshArgs, mGeomSnapshots);
 }
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
index eb14933..ce602a8 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
@@ -72,6 +72,10 @@
     mConsumer->setDefaultBufferSize(limitedSize.width, limitedSize.height);
     mConsumer->setMaxAcquiredBufferCount(
             SurfaceFlinger::maxFrameBufferAcquiredBuffers - 1);
+
+    for (size_t i = 0; i < sizeof(mHwcBufferIds) / sizeof(mHwcBufferIds[0]); ++i) {
+        mHwcBufferIds[i] = UINT64_MAX;
+    }
 }
 
 void FramebufferSurface::resizeBuffers(const ui::Size& newSize) {
@@ -88,31 +92,16 @@
 }
 
 status_t FramebufferSurface::advanceFrame() {
-    uint32_t slot = 0;
-    sp<GraphicBuffer> buf;
-    sp<Fence> acquireFence(Fence::NO_FENCE);
-    Dataspace dataspace = Dataspace::UNKNOWN;
-    status_t result = nextBuffer(slot, buf, acquireFence, dataspace);
-    mDataSpace = dataspace;
-    if (result != NO_ERROR) {
-        ALOGE("error latching next FramebufferSurface buffer: %s (%d)",
-                strerror(-result), result);
-    }
-    return result;
-}
-
-status_t FramebufferSurface::nextBuffer(uint32_t& outSlot,
-        sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence,
-        Dataspace& outDataspace) {
     Mutex::Autolock lock(mMutex);
 
     BufferItem item;
     status_t err = acquireBufferLocked(&item, 0);
     if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
-        mHwcBufferCache.getHwcBuffer(mCurrentBufferSlot, mCurrentBuffer, &outSlot, &outBuffer);
+        mDataspace = Dataspace::UNKNOWN;
         return NO_ERROR;
     } else if (err != NO_ERROR) {
         ALOGE("error acquiring buffer: %s (%d)", strerror(-err), err);
+        mDataspace = Dataspace::UNKNOWN;
         return err;
     }
 
@@ -133,13 +122,18 @@
     mCurrentBufferSlot = item.mSlot;
     mCurrentBuffer = mSlots[mCurrentBufferSlot].mGraphicBuffer;
     mCurrentFence = item.mFence;
+    mDataspace = static_cast<Dataspace>(item.mDataSpace);
 
-    outFence = item.mFence;
-    mHwcBufferCache.getHwcBuffer(mCurrentBufferSlot, mCurrentBuffer, &outSlot, &outBuffer);
-    outDataspace = static_cast<Dataspace>(item.mDataSpace);
-    status_t result = mHwc.setClientTarget(mDisplayId, outSlot, outFence, outBuffer, outDataspace);
+    // assume HWC has previously seen the buffer in this slot
+    sp<GraphicBuffer> hwcBuffer = sp<GraphicBuffer>(nullptr);
+    if (mCurrentBuffer->getId() != mHwcBufferIds[mCurrentBufferSlot]) {
+        mHwcBufferIds[mCurrentBufferSlot] = mCurrentBuffer->getId();
+        hwcBuffer = mCurrentBuffer; // HWC hasn't previously seen this buffer in this slot
+    }
+    status_t result = mHwc.setClientTarget(mDisplayId, mCurrentBufferSlot, mCurrentFence, hwcBuffer,
+                                           mDataspace);
     if (result != NO_ERROR) {
-        ALOGE("error posting framebuffer: %d", result);
+        ALOGE("error posting framebuffer: %s (%d)", strerror(-result), result);
         return result;
     }
 
@@ -190,7 +184,7 @@
         limitedSize.width = maxSize.height * aspectRatio;
         wasLimited = true;
     }
-    ALOGI_IF(wasLimited, "framebuffer size has been limited to [%dx%d] from [%dx%d]",
+    ALOGI_IF(wasLimited, "Framebuffer size has been limited to [%dx%d] from [%dx%d]",
              limitedSize.width, limitedSize.height, size.width, size.height);
     return limitedSize;
 }
@@ -198,9 +192,9 @@
 void FramebufferSurface::dumpAsString(String8& result) const {
     Mutex::Autolock lock(mMutex);
     result.append("   FramebufferSurface\n");
-    result.appendFormat("      mDataSpace=%s (%d)\n",
-                        dataspaceDetails(static_cast<android_dataspace>(mDataSpace)).c_str(),
-                        mDataSpace);
+    result.appendFormat("      mDataspace=%s (%d)\n",
+                        dataspaceDetails(static_cast<android_dataspace>(mDataspace)).c_str(),
+                        mDataspace);
     ConsumerBase::dumpLocked(result, "      ");
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
index d41a856..0b863da 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
@@ -21,7 +21,7 @@
 #include <sys/types.h>
 
 #include <compositionengine/DisplaySurface.h>
-#include <compositionengine/impl/HwcBufferCache.h>
+#include <gui/BufferQueue.h>
 #include <gui/ConsumerBase.h>
 #include <ui/DisplayId.h>
 #include <ui/Size.h>
@@ -69,12 +69,6 @@
 
     virtual void dumpLocked(String8& result, const char* prefix) const;
 
-    // nextBuffer waits for and then latches the next buffer from the
-    // BufferQueue and releases the previously latched buffer to the
-    // BufferQueue.  The new buffer is returned in the 'buffer' argument.
-    status_t nextBuffer(uint32_t& outSlot, sp<GraphicBuffer>& outBuffer,
-            sp<Fence>& outFence, ui::Dataspace& outDataspace);
-
     const PhysicalDisplayId mDisplayId;
 
     // Framebuffer size has a dimension limitation in pixels based on the graphics capabilities of
@@ -91,7 +85,7 @@
     // compositing. Otherwise it will display the dataspace of the buffer
     // use for compositing which can change as wide-color content is
     // on/off.
-    ui::Dataspace mDataSpace;
+    ui::Dataspace mDataspace;
 
     // mCurrentBuffer is the current buffer or nullptr to indicate that there is
     // no current buffer.
@@ -103,7 +97,9 @@
     // Hardware composer, owned by SurfaceFlinger.
     HWComposer& mHwc;
 
-    compositionengine::impl::HwcBufferCache mHwcBufferCache;
+    // Buffers that HWC has seen before, indexed by slot number.
+    // NOTE: The BufferQueue slot number is the same as the HWC slot number.
+    uint64_t mHwcBufferIds[BufferQueue::NUM_BUFFER_SLOTS];
 
     // Previous buffer to release after getting an updated retire fence
     bool mHasPendingRelease;
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index 3803a78..d62075e 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -103,6 +103,10 @@
     sink->setAsyncMode(true);
     IGraphicBufferProducer::QueueBufferOutput output;
     mSource[SOURCE_SCRATCH]->connect(nullptr, NATIVE_WINDOW_API_EGL, false, &output);
+
+    for (size_t i = 0; i < sizeof(mHwcBufferIds) / sizeof(mHwcBufferIds[0]); ++i) {
+        mHwcBufferIds[i] = UINT64_MAX;
+    }
 }
 
 VirtualDisplaySurface::~VirtualDisplaySurface() {
@@ -197,9 +201,9 @@
         return NO_MEMORY;
     }
 
-    sp<GraphicBuffer> fbBuffer = mFbProducerSlot >= 0 ?
-            mProducerBuffers[mFbProducerSlot] : sp<GraphicBuffer>(nullptr);
-    sp<GraphicBuffer> outBuffer = mProducerBuffers[mOutputProducerSlot];
+    sp<GraphicBuffer> const& fbBuffer =
+            mFbProducerSlot >= 0 ? mProducerBuffers[mFbProducerSlot] : sp<GraphicBuffer>(nullptr);
+    sp<GraphicBuffer> const& outBuffer = mProducerBuffers[mOutputProducerSlot];
     VDS_LOGV("%s: fb=%d(%p) out=%d(%p)", __func__, mFbProducerSlot, fbBuffer.get(),
              mOutputProducerSlot, outBuffer.get());
 
@@ -211,12 +215,14 @@
 
     status_t result = NO_ERROR;
     if (fbBuffer != nullptr) {
-        uint32_t hwcSlot = 0;
-        sp<GraphicBuffer> hwcBuffer;
-        mHwcBufferCache.getHwcBuffer(mFbProducerSlot, fbBuffer, &hwcSlot, &hwcBuffer);
-
+        // assume that HWC has previously seen the buffer in this slot
+        sp<GraphicBuffer> hwcBuffer = sp<GraphicBuffer>(nullptr);
+        if (fbBuffer->getId() != mHwcBufferIds[mFbProducerSlot]) {
+            mHwcBufferIds[mFbProducerSlot] = fbBuffer->getId();
+            hwcBuffer = fbBuffer; // HWC hasn't previously seen this buffer in this slot
+        }
         // TODO: Correctly propagate the dataspace from GL composition
-        result = mHwc.setClientTarget(*halDisplayId, hwcSlot, mFbFence, hwcBuffer,
+        result = mHwc.setClientTarget(*halDisplayId, mFbProducerSlot, mFbFence, hwcBuffer,
                                       ui::Dataspace::UNKNOWN);
     }
 
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index e21095a..be06e2b 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -20,7 +20,7 @@
 #include <string>
 
 #include <compositionengine/DisplaySurface.h>
-#include <compositionengine/impl/HwcBufferCache.h>
+#include <gui/BufferQueue.h>
 #include <gui/ConsumerBase.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <ui/DisplayId.h>
@@ -164,6 +164,10 @@
     sp<IGraphicBufferProducer> mSource[2]; // indexed by SOURCE_*
     uint32_t mDefaultOutputFormat;
 
+    // Buffers that HWC has seen before, indexed by HWC slot number.
+    // NOTE: The BufferQueue slot number is the same as the HWC slot number.
+    uint64_t mHwcBufferIds[BufferQueue::NUM_BUFFER_SLOTS];
+
     //
     // Inter-frame state
     //
@@ -260,8 +264,6 @@
 
     bool mMustRecompose = false;
 
-    compositionengine::impl::HwcBufferCache mHwcBufferCache;
-
     bool mForceHwcCopy;
 };
 
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
new file mode 100644
index 0000000..db4e8af
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#undef LOG_TAG
+#define LOG_TAG "LayerHierarchy"
+
+#include "LayerHierarchy.h"
+#include "SwapErase.h"
+
+namespace android::surfaceflinger::frontend {
+
+namespace {
+auto layerZCompare = [](const std::pair<LayerHierarchy*, LayerHierarchy::Variant>& lhs,
+                        const std::pair<LayerHierarchy*, LayerHierarchy::Variant>& rhs) {
+    auto lhsLayer = lhs.first->getLayer();
+    auto rhsLayer = rhs.first->getLayer();
+    if (lhsLayer->layerStack != rhsLayer->layerStack) {
+        return lhsLayer->layerStack.id < rhsLayer->layerStack.id;
+    }
+    if (lhsLayer->z != rhsLayer->z) {
+        return lhsLayer->z < rhsLayer->z;
+    }
+    return lhsLayer->id < rhsLayer->id;
+};
+
+void insertSorted(std::vector<std::pair<LayerHierarchy*, LayerHierarchy::Variant>>& vec,
+                  std::pair<LayerHierarchy*, LayerHierarchy::Variant> value) {
+    auto it = std::upper_bound(vec.begin(), vec.end(), value, layerZCompare);
+    vec.insert(it, std::move(value));
+}
+} // namespace
+
+LayerHierarchy::LayerHierarchy(RequestedLayerState* layer) : mLayer(layer) {}
+
+LayerHierarchy::LayerHierarchy(const LayerHierarchy& hierarchy, bool childrenOnly) {
+    mLayer = (childrenOnly) ? nullptr : hierarchy.mLayer;
+    mChildren = hierarchy.mChildren;
+}
+
+void LayerHierarchy::traverse(const Visitor& visitor,
+                              LayerHierarchy::TraversalPath& traversalPath) const {
+    if (mLayer) {
+        bool breakTraversal = !visitor(*this, traversalPath);
+        if (breakTraversal) {
+            return;
+        }
+    }
+    if (traversalPath.hasRelZLoop()) {
+        LOG_ALWAYS_FATAL("Found relative z loop layerId:%d", traversalPath.invalidRelativeRootId);
+    }
+    for (auto& [child, childVariant] : mChildren) {
+        ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id,
+                                                         childVariant);
+        child->traverse(visitor, traversalPath);
+    }
+}
+
+void LayerHierarchy::traverseInZOrder(const Visitor& visitor,
+                                      LayerHierarchy::TraversalPath& traversalPath) const {
+    bool traverseThisLayer = (mLayer != nullptr);
+    for (auto it = mChildren.begin(); it < mChildren.end(); it++) {
+        auto& [child, childVariant] = *it;
+        if (traverseThisLayer && child->getLayer()->z >= 0) {
+            bool breakTraversal = !visitor(*this, traversalPath);
+            if (breakTraversal) {
+                return;
+            }
+            traverseThisLayer = false;
+        }
+        if (childVariant == LayerHierarchy::Variant::Detached) {
+            continue;
+        }
+        ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id,
+                                                         childVariant);
+        child->traverseInZOrder(visitor, traversalPath);
+    }
+
+    if (traverseThisLayer) {
+        visitor(*this, traversalPath);
+    }
+}
+
+void LayerHierarchy::addChild(LayerHierarchy* child, LayerHierarchy::Variant variant) {
+    insertSorted(mChildren, {child, variant});
+}
+
+void LayerHierarchy::removeChild(LayerHierarchy* child) {
+    auto it = std::find_if(mChildren.begin(), mChildren.end(),
+                           [child](const std::pair<LayerHierarchy*, Variant>& x) {
+                               return x.first == child;
+                           });
+    if (it == mChildren.end()) {
+        LOG_ALWAYS_FATAL("Could not find child!");
+    }
+    mChildren.erase(it);
+}
+
+void LayerHierarchy::sortChildrenByZOrder() {
+    std::sort(mChildren.begin(), mChildren.end(), layerZCompare);
+}
+
+void LayerHierarchy::updateChild(LayerHierarchy* hierarchy, LayerHierarchy::Variant variant) {
+    auto it = std::find_if(mChildren.begin(), mChildren.end(),
+                           [hierarchy](std::pair<LayerHierarchy*, Variant>& child) {
+                               return child.first == hierarchy;
+                           });
+    if (it == mChildren.end()) {
+        LOG_ALWAYS_FATAL("Could not find child!");
+    } else {
+        it->second = variant;
+    }
+}
+
+const RequestedLayerState* LayerHierarchy::getLayer() const {
+    return mLayer;
+}
+
+std::string LayerHierarchy::getDebugStringShort() const {
+    std::string debug = "LayerHierarchy{";
+    debug += ((mLayer) ? mLayer->getDebugStringShort() : "root") + " ";
+    if (mChildren.empty()) {
+        debug += "no children";
+    } else {
+        debug += std::to_string(mChildren.size()) + " children";
+    }
+    return debug + "}";
+}
+
+std::string LayerHierarchy::getDebugString(const char* prefix) const {
+    std::string debug = prefix + getDebugStringShort();
+    for (auto& [child, childVariant] : mChildren) {
+        std::string childPrefix = "  " + std::string(prefix) + " " + std::to_string(childVariant);
+        debug += "\n" + child->getDebugString(childPrefix.c_str());
+    }
+    return debug;
+}
+
+bool LayerHierarchy::hasRelZLoop(uint32_t& outInvalidRelativeRoot) const {
+    outInvalidRelativeRoot = UNASSIGNED_LAYER_ID;
+    traverse([&outInvalidRelativeRoot](const LayerHierarchy&,
+                                       const LayerHierarchy::TraversalPath& traversalPath) -> bool {
+        if (traversalPath.hasRelZLoop()) {
+            outInvalidRelativeRoot = traversalPath.invalidRelativeRootId;
+            return false;
+        }
+        return true;
+    });
+    return outInvalidRelativeRoot != UNASSIGNED_LAYER_ID;
+}
+
+LayerHierarchyBuilder::LayerHierarchyBuilder(
+        const std::vector<std::unique_ptr<RequestedLayerState>>& layers) {
+    mHierarchies.reserve(layers.size());
+    mLayerIdToHierarchy.reserve(layers.size());
+    for (auto& layer : layers) {
+        mHierarchies.emplace_back(std::make_unique<LayerHierarchy>(layer.get()));
+        mLayerIdToHierarchy[layer->id] = mHierarchies.back().get();
+    }
+    for (const auto& layer : layers) {
+        onLayerAdded(layer.get());
+    }
+    detachHierarchyFromRelativeParent(&mOffscreenRoot);
+}
+
+void LayerHierarchyBuilder::attachToParent(LayerHierarchy* hierarchy) {
+    auto layer = hierarchy->mLayer;
+    LayerHierarchy::Variant type = layer->hasValidRelativeParent()
+            ? LayerHierarchy::Variant::Detached
+            : LayerHierarchy::Variant::Attached;
+
+    LayerHierarchy* parent;
+
+    if (layer->parentId != UNASSIGNED_LAYER_ID) {
+        parent = getHierarchyFromId(layer->parentId);
+    } else if (layer->canBeRoot) {
+        parent = &mRoot;
+    } else {
+        parent = &mOffscreenRoot;
+    }
+    parent->addChild(hierarchy, type);
+    hierarchy->mParent = parent;
+}
+
+void LayerHierarchyBuilder::detachFromParent(LayerHierarchy* hierarchy) {
+    hierarchy->mParent->removeChild(hierarchy);
+    hierarchy->mParent = nullptr;
+}
+
+void LayerHierarchyBuilder::attachToRelativeParent(LayerHierarchy* hierarchy) {
+    auto layer = hierarchy->mLayer;
+    if (!layer->hasValidRelativeParent() || hierarchy->mRelativeParent) {
+        return;
+    }
+
+    if (layer->relativeParentId != UNASSIGNED_LAYER_ID) {
+        hierarchy->mRelativeParent = getHierarchyFromId(layer->relativeParentId);
+    } else {
+        hierarchy->mRelativeParent = &mOffscreenRoot;
+    }
+    hierarchy->mRelativeParent->addChild(hierarchy, LayerHierarchy::Variant::Relative);
+    hierarchy->mParent->updateChild(hierarchy, LayerHierarchy::Variant::Detached);
+}
+
+void LayerHierarchyBuilder::detachFromRelativeParent(LayerHierarchy* hierarchy) {
+    if (hierarchy->mRelativeParent) {
+        hierarchy->mRelativeParent->removeChild(hierarchy);
+    }
+    hierarchy->mRelativeParent = nullptr;
+    hierarchy->mParent->updateChild(hierarchy, LayerHierarchy::Variant::Attached);
+}
+
+void LayerHierarchyBuilder::attachHierarchyToRelativeParent(LayerHierarchy* root) {
+    if (root->mLayer) {
+        attachToRelativeParent(root);
+    }
+    for (auto& [child, childVariant] : root->mChildren) {
+        if (childVariant == LayerHierarchy::Variant::Detached ||
+            childVariant == LayerHierarchy::Variant::Attached) {
+            attachHierarchyToRelativeParent(child);
+        }
+    }
+}
+
+void LayerHierarchyBuilder::detachHierarchyFromRelativeParent(LayerHierarchy* root) {
+    if (root->mLayer) {
+        detachFromRelativeParent(root);
+    }
+    for (auto& [child, childVariant] : root->mChildren) {
+        if (childVariant == LayerHierarchy::Variant::Detached ||
+            childVariant == LayerHierarchy::Variant::Attached) {
+            detachHierarchyFromRelativeParent(child);
+        }
+    }
+}
+
+void LayerHierarchyBuilder::onLayerAdded(RequestedLayerState* layer) {
+    LayerHierarchy* hierarchy = getHierarchyFromId(layer->id);
+    attachToParent(hierarchy);
+    attachToRelativeParent(hierarchy);
+
+    if (layer->mirrorId != UNASSIGNED_LAYER_ID) {
+        LayerHierarchy* mirror = getHierarchyFromId(layer->mirrorId);
+        hierarchy->addChild(mirror, LayerHierarchy::Variant::Mirror);
+    }
+}
+
+void LayerHierarchyBuilder::onLayerDestroyed(RequestedLayerState* layer) {
+    LayerHierarchy* hierarchy = getHierarchyFromId(layer->id, /*crashOnFailure=*/false);
+    if (!hierarchy) {
+        // Layer was never part of the hierarchy if it was created and destroyed in the same
+        // transaction.
+        return;
+    }
+    // detach from parent
+    detachFromRelativeParent(hierarchy);
+    detachFromParent(hierarchy);
+
+    // detach children
+    for (auto& [child, variant] : hierarchy->mChildren) {
+        if (variant == LayerHierarchy::Variant::Attached ||
+            variant == LayerHierarchy::Variant::Detached) {
+            mOffscreenRoot.addChild(child, LayerHierarchy::Variant::Attached);
+            child->mParent = &mOffscreenRoot;
+        } else if (variant == LayerHierarchy::Variant::Relative) {
+            mOffscreenRoot.addChild(child, LayerHierarchy::Variant::Attached);
+            child->mRelativeParent = &mOffscreenRoot;
+        }
+    }
+
+    swapErase(mHierarchies, [hierarchy](std::unique_ptr<LayerHierarchy>& layerHierarchy) {
+        return layerHierarchy.get() == hierarchy;
+    });
+    mLayerIdToHierarchy.erase(layer->id);
+}
+
+void LayerHierarchyBuilder::updateMirrorLayer(RequestedLayerState* layer) {
+    LayerHierarchy* hierarchy = getHierarchyFromId(layer->id);
+    auto it = hierarchy->mChildren.begin();
+    while (it != hierarchy->mChildren.end()) {
+        if (it->second == LayerHierarchy::Variant::Mirror) {
+            hierarchy->mChildren.erase(it);
+            break;
+        }
+        it++;
+    }
+
+    if (layer->mirrorId != UNASSIGNED_LAYER_ID) {
+        hierarchy->addChild(getHierarchyFromId(layer->mirrorId), LayerHierarchy::Variant::Mirror);
+    }
+}
+
+void LayerHierarchyBuilder::update(
+        const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
+        const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers) {
+    // rebuild map
+    for (auto& layer : layers) {
+        if (layer->changes.test(RequestedLayerState::Changes::Created)) {
+            mHierarchies.emplace_back(std::make_unique<LayerHierarchy>(layer.get()));
+            mLayerIdToHierarchy[layer->id] = mHierarchies.back().get();
+        }
+    }
+
+    for (auto& layer : layers) {
+        if (layer->changes.get() == 0) {
+            continue;
+        }
+        if (layer->changes.test(RequestedLayerState::Changes::Created)) {
+            onLayerAdded(layer.get());
+            continue;
+        }
+        LayerHierarchy* hierarchy = getHierarchyFromId(layer->id);
+        if (layer->changes.test(RequestedLayerState::Changes::Parent)) {
+            detachFromParent(hierarchy);
+            attachToParent(hierarchy);
+        }
+        if (layer->changes.test(RequestedLayerState::Changes::RelativeParent)) {
+            detachFromRelativeParent(hierarchy);
+            attachToRelativeParent(hierarchy);
+        }
+        if (layer->changes.test(RequestedLayerState::Changes::Z)) {
+            hierarchy->mParent->sortChildrenByZOrder();
+            if (hierarchy->mRelativeParent) {
+                hierarchy->mRelativeParent->sortChildrenByZOrder();
+            }
+        }
+        if (layer->changes.test(RequestedLayerState::Changes::Mirror)) {
+            updateMirrorLayer(layer.get());
+        }
+    }
+
+    for (auto& layer : destroyedLayers) {
+        onLayerDestroyed(layer.get());
+    }
+    // When moving from onscreen to offscreen and vice versa, we need to attach and detach
+    // from our relative parents. This walks down both trees to do so. We can optimize this
+    // further by tracking onscreen, offscreen state in LayerHierarchy.
+    detachHierarchyFromRelativeParent(&mOffscreenRoot);
+    attachHierarchyToRelativeParent(&mRoot);
+}
+
+const LayerHierarchy& LayerHierarchyBuilder::getHierarchy() const {
+    return mRoot;
+}
+
+const LayerHierarchy& LayerHierarchyBuilder::getOffscreenHierarchy() const {
+    return mOffscreenRoot;
+}
+
+std::string LayerHierarchyBuilder::getDebugString(uint32_t layerId, uint32_t depth) const {
+    if (depth > 10) return "too deep, loop?";
+    if (layerId == UNASSIGNED_LAYER_ID) return "";
+    auto it = mLayerIdToHierarchy.find(layerId);
+    if (it == mLayerIdToHierarchy.end()) return "not found";
+
+    LayerHierarchy* hierarchy = it->second;
+    if (!hierarchy->mLayer) return "none";
+
+    std::string debug =
+            "[" + std::to_string(hierarchy->mLayer->id) + "] " + hierarchy->mLayer->name;
+    if (hierarchy->mRelativeParent) {
+        debug += " Relative:" + hierarchy->mRelativeParent->getDebugStringShort();
+    }
+    if (hierarchy->mParent) {
+        debug += " Parent:" + hierarchy->mParent->getDebugStringShort();
+    }
+    return debug;
+}
+
+LayerHierarchy LayerHierarchyBuilder::getPartialHierarchy(uint32_t layerId,
+                                                          bool childrenOnly) const {
+    auto it = mLayerIdToHierarchy.find(layerId);
+    if (it == mLayerIdToHierarchy.end()) return {nullptr};
+
+    LayerHierarchy hierarchy(*it->second, childrenOnly);
+    return hierarchy;
+}
+
+LayerHierarchy* LayerHierarchyBuilder::getHierarchyFromId(uint32_t layerId, bool crashOnFailure) {
+    auto it = mLayerIdToHierarchy.find(layerId);
+    if (it == mLayerIdToHierarchy.end()) {
+        if (crashOnFailure) {
+            LOG_ALWAYS_FATAL("Could not find hierarchy for layer id %d", layerId);
+        }
+        return nullptr;
+    };
+
+    return it->second;
+}
+
+LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::ROOT_TRAVERSAL_ID =
+        {.id = UNASSIGNED_LAYER_ID, .variant = LayerHierarchy::Attached};
+
+std::string LayerHierarchy::TraversalPath::toString() const {
+    std::string debugString = "TraversalPath{.id = " + std::to_string(id);
+
+    if (!mirrorRootIds.empty()) {
+        debugString += ", .mirrorRootIds=";
+        for (auto rootId : mirrorRootIds) {
+            debugString += std::to_string(rootId) + ",";
+        }
+    }
+
+    if (!relativeRootIds.empty()) {
+        debugString += ", .relativeRootIds=";
+        for (auto rootId : relativeRootIds) {
+            debugString += std::to_string(rootId) + ",";
+        }
+    }
+
+    if (hasRelZLoop()) {
+        debugString += ", hasRelZLoop=true invalidRelativeRootId=";
+        debugString += std::to_string(invalidRelativeRootId) + ",";
+    }
+
+    debugString += "}";
+    return debugString;
+}
+
+// Helper class to update a passed in TraversalPath when visiting a child. When the object goes out
+// of scope the TraversalPath is reset to its original state.
+LayerHierarchy::ScopedAddToTraversalPath::ScopedAddToTraversalPath(TraversalPath& traversalPath,
+                                                                   uint32_t layerId,
+                                                                   LayerHierarchy::Variant variant)
+      : mTraversalPath(traversalPath),
+        mParentId(traversalPath.id),
+        mParentVariant(traversalPath.variant) {
+    // Update the traversal id with the child layer id and variant. Parent id and variant are
+    // stored to reset the id upon destruction.
+    traversalPath.id = layerId;
+    traversalPath.variant = variant;
+    if (variant == LayerHierarchy::Variant::Mirror) {
+        traversalPath.mirrorRootIds.emplace_back(layerId);
+    }
+    if (variant == LayerHierarchy::Variant::Relative) {
+        if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(),
+                      layerId) != traversalPath.relativeRootIds.end()) {
+            traversalPath.invalidRelativeRootId = layerId;
+        }
+        traversalPath.relativeRootIds.emplace_back(layerId);
+    }
+}
+LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() {
+    // Reset the traversal id to its original parent state using the state that was saved in
+    // the constructor.
+    if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) {
+        mTraversalPath.mirrorRootIds.pop_back();
+    }
+    if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
+        mTraversalPath.relativeRootIds.pop_back();
+    }
+    if (mTraversalPath.invalidRelativeRootId == mTraversalPath.id) {
+        mTraversalPath.invalidRelativeRootId = UNASSIGNED_LAYER_ID;
+    }
+    mTraversalPath.id = mParentId;
+    mTraversalPath.variant = mParentVariant;
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
new file mode 100644
index 0000000..f83a859
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2022 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 "FrontEnd/LayerCreationArgs.h"
+#include "RequestedLayerState.h"
+#include "ftl/small_vector.h"
+
+namespace android::surfaceflinger::frontend {
+class LayerHierarchyBuilder;
+
+// LayerHierarchy allows us to navigate the layer hierarchy in z-order, or depth first traversal.
+// The hierarchy is created from a set of RequestedLayerStates. The hierarchy itself does not
+// contain additional states. Instead, it is a representation of RequestedLayerStates as a graph.
+//
+// Each node in the hierarchy can be visited by multiple parents (making this a graph). While
+// traversing the hierarchy, a new concept called Variant can be used to understand the
+// relationship of the layer to its parent. The following variants are possible:
+// Attached - child of the parent
+// Detached - child of the parent but currently relative parented to another layer
+// Relative - relative child of the parent
+// Mirror - mirrored from another layer
+//
+// By representing the hierarchy as a graph, we can represent mirrored layer hierarchies without
+// cloning the layer requested state. The mirrored hierarchy and its corresponding
+// RequestedLayerStates are kept in sync because the mirrored hierarchy does not clone any
+// states.
+class LayerHierarchy {
+public:
+    enum Variant {
+        Attached,
+        Detached,
+        Relative,
+        Mirror,
+    };
+    // Represents a unique path to a node.
+    struct TraversalPath {
+        uint32_t id;
+        LayerHierarchy::Variant variant;
+        // Mirrored layers can have a different geometry than their parents so we need to track
+        // the mirror roots in the traversal.
+        ftl::SmallVector<uint32_t, 5> mirrorRootIds;
+        // Relative layers can be visited twice, once by their parent and then once again by
+        // their relative parent. We keep track of the roots here to detect any loops in the
+        // hierarchy. If a relative root already exists in the list while building the
+        // TraversalPath, it means that somewhere in the hierarchy two layers are relatively
+        // parented to each other.
+        ftl::SmallVector<uint32_t, 5> relativeRootIds;
+        // First duplicate relative root id found. If this is a valid layer id that means we are
+        // in a loop.
+        uint32_t invalidRelativeRootId = UNASSIGNED_LAYER_ID;
+        bool hasRelZLoop() const { return invalidRelativeRootId != UNASSIGNED_LAYER_ID; }
+        bool isRelative() { return !relativeRootIds.empty(); }
+
+        bool operator==(const TraversalPath& other) const {
+            return id == other.id && mirrorRootIds == other.mirrorRootIds;
+        }
+        std::string toString() const;
+
+        static TraversalPath ROOT_TRAVERSAL_ID;
+    };
+
+    // Helper class to add nodes to an existing traversal id and removes the
+    // node when it goes out of scope.
+    class ScopedAddToTraversalPath {
+    public:
+        ScopedAddToTraversalPath(TraversalPath& traversalPath, uint32_t layerId,
+                                 LayerHierarchy::Variant variantArg);
+        ~ScopedAddToTraversalPath();
+
+    private:
+        TraversalPath& mTraversalPath;
+        uint32_t mParentId;
+        LayerHierarchy::Variant mParentVariant;
+    };
+    LayerHierarchy(RequestedLayerState* layer);
+
+    // Visitor function that provides the hierarchy node and a traversal id which uniquely
+    // identifies how was visited. The hierarchy contains a pointer to the RequestedLayerState.
+    // Return false to stop traversing down the hierarchy.
+    typedef std::function<bool(const LayerHierarchy& hierarchy,
+                               const LayerHierarchy::TraversalPath& traversalPath)>
+            Visitor;
+
+    // Traverse the hierarchy and visit all child variants.
+    void traverse(const Visitor& visitor) const {
+        traverse(visitor, TraversalPath::ROOT_TRAVERSAL_ID);
+    }
+
+    // Traverse the hierarchy in z-order, skipping children that have relative parents.
+    void traverseInZOrder(const Visitor& visitor) const {
+        traverseInZOrder(visitor, TraversalPath::ROOT_TRAVERSAL_ID);
+    }
+
+    const RequestedLayerState* getLayer() const;
+    std::string getDebugString(const char* prefix = "") const;
+    std::string getDebugStringShort() const;
+    // Traverse the hierarchy and return true if loops are found. The outInvalidRelativeRoot
+    // will contain the first relative root that was visited twice in a traversal.
+    bool hasRelZLoop(uint32_t& outInvalidRelativeRoot) const;
+    std::vector<std::pair<LayerHierarchy*, Variant>> mChildren;
+
+private:
+    friend LayerHierarchyBuilder;
+    LayerHierarchy(const LayerHierarchy& hierarchy, bool childrenOnly);
+    void addChild(LayerHierarchy*, LayerHierarchy::Variant);
+    void removeChild(LayerHierarchy*);
+    void sortChildrenByZOrder();
+    void updateChild(LayerHierarchy*, LayerHierarchy::Variant);
+    void traverseInZOrder(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const;
+    void traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const;
+
+    const RequestedLayerState* mLayer;
+    LayerHierarchy* mParent = nullptr;
+    LayerHierarchy* mRelativeParent = nullptr;
+};
+
+// Given a list of RequestedLayerState, this class will build a root hierarchy and an
+// offscreen hierarchy. The builder also has an update method which can update an existing
+// hierarchy from a list of RequestedLayerState and associated change flags.
+class LayerHierarchyBuilder {
+public:
+    LayerHierarchyBuilder(const std::vector<std::unique_ptr<RequestedLayerState>>&);
+    void update(const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
+                const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers);
+    LayerHierarchy getPartialHierarchy(uint32_t, bool childrenOnly) const;
+    const LayerHierarchy& getHierarchy() const;
+    const LayerHierarchy& getOffscreenHierarchy() const;
+    std::string getDebugString(uint32_t layerId, uint32_t depth = 0) const;
+
+private:
+    void onLayerAdded(RequestedLayerState* layer);
+    void attachToParent(LayerHierarchy*);
+    void detachFromParent(LayerHierarchy*);
+    void attachToRelativeParent(LayerHierarchy*);
+    void detachFromRelativeParent(LayerHierarchy*);
+    void attachHierarchyToRelativeParent(LayerHierarchy*);
+    void detachHierarchyFromRelativeParent(LayerHierarchy*);
+
+    void onLayerDestroyed(RequestedLayerState* layer);
+    void updateMirrorLayer(RequestedLayerState* layer);
+    LayerHierarchy* getHierarchyFromId(uint32_t layerId, bool crashOnFailure = true);
+    std::unordered_map<uint32_t, LayerHierarchy*> mLayerIdToHierarchy;
+    std::vector<std::unique_ptr<LayerHierarchy>> mHierarchies;
+    LayerHierarchy mRoot{nullptr};
+    LayerHierarchy mOffscreenRoot{nullptr};
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 7afa144..fdf60b3 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -42,10 +42,10 @@
                              it->second.owner.getDebugString().c_str());
         }
 
-        linkLayer(layer.parentId, layer.id);
-        linkLayer(layer.relativeParentId, layer.id);
-        linkLayer(layer.mirrorId, layer.id);
-        linkLayer(layer.touchCropId, layer.id);
+        layer.parentId = linkLayer(layer.parentId, layer.id);
+        layer.relativeParentId = linkLayer(layer.relativeParentId, layer.id);
+        layer.mirrorId = linkLayer(layer.mirrorId, layer.id);
+        layer.touchCropId = linkLayer(layer.touchCropId, layer.id);
 
         mLayers.emplace_back(std::move(newLayer));
     }
@@ -83,10 +83,10 @@
 
         RequestedLayerState& layer = it->second.owner;
 
-        unlinkLayer(layer.parentId, layer.id);
-        unlinkLayer(layer.relativeParentId, layer.id);
-        unlinkLayer(layer.mirrorId, layer.id);
-        unlinkLayer(layer.touchCropId, layer.id);
+        layer.parentId = unlinkLayer(layer.parentId, layer.id);
+        layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id);
+        layer.mirrorId = unlinkLayer(layer.mirrorId, layer.id);
+        layer.touchCropId = unlinkLayer(layer.touchCropId, layer.id);
 
         auto& references = it->second.references;
         for (uint32_t linkedLayerId : references) {
@@ -199,15 +199,15 @@
 
             if (oldParentId != layer->parentId) {
                 unlinkLayer(oldParentId, layer->id);
-                linkLayer(layer->parentId, layer->id);
+                layer->parentId = linkLayer(layer->parentId, layer->id);
             }
             if (oldRelativeParentId != layer->relativeParentId) {
                 unlinkLayer(oldRelativeParentId, layer->id);
-                linkLayer(layer->relativeParentId, layer->id);
+                layer->relativeParentId = linkLayer(layer->relativeParentId, layer->id);
             }
             if (oldTouchCropId != layer->touchCropId) {
                 unlinkLayer(oldTouchCropId, layer->id);
-                linkLayer(layer->touchCropId, layer->id);
+                layer->touchCropId = linkLayer(layer->touchCropId, layer->id);
             }
 
             mGlobalChanges |= layer->changes &
@@ -287,26 +287,28 @@
     return &it->second.references;
 }
 
-void LayerLifecycleManager::linkLayer(uint32_t layerId, uint32_t layerToLink) {
-    if (layerToLink && layerId != UNASSIGNED_LAYER_ID) {
-        std::vector<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
-        if (!linkedLayers) {
-            LOG_ALWAYS_FATAL("Could not find layer id %d to link %d", layerId, layerToLink);
-            return;
-        }
-        linkedLayers->emplace_back(layerToLink);
+uint32_t LayerLifecycleManager::linkLayer(uint32_t layerId, uint32_t layerToLink) {
+    if (layerId == UNASSIGNED_LAYER_ID) {
+        return UNASSIGNED_LAYER_ID;
     }
-}
-
-void LayerLifecycleManager::unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer) {
-    uint32_t layerId = inOutLayerId;
-    inOutLayerId = UNASSIGNED_LAYER_ID;
 
     std::vector<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
     if (!linkedLayers) {
-        return;
+        ALOGV("Could not find layer id %d to link %d. Parent is probably destroyed", layerId,
+              layerToLink);
+        return UNASSIGNED_LAYER_ID;
+    }
+    linkedLayers->emplace_back(layerToLink);
+    return layerId;
+}
+
+uint32_t LayerLifecycleManager::unlinkLayer(uint32_t layerId, uint32_t linkedLayer) {
+    std::vector<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
+    if (!linkedLayers) {
+        return UNASSIGNED_LAYER_ID;
     }
     swapErase(*linkedLayers, linkedLayer);
+    return UNASSIGNED_LAYER_ID;
 }
 
 std::string LayerLifecycleManager::References::getDebugString() const {
@@ -318,4 +320,16 @@
     return debugInfo;
 }
 
+void LayerLifecycleManager::fixRelativeZLoop(uint32_t relativeRootId) {
+    auto it = mIdToLayer.find(relativeRootId);
+    if (it == mIdToLayer.end()) {
+        return;
+    }
+    RequestedLayerState& layer = it->second.owner;
+    layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id);
+    layer.changes |=
+            RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::RelativeParent;
+    mGlobalChanges |= RequestedLayerState::Changes::Hierarchy;
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
index ad70d3f..63a7afc 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -42,6 +42,12 @@
     void applyTransactions(const std::vector<TransactionState>&);
     void onHandlesDestroyed(const std::vector<uint32_t>&);
 
+    // Detaches the layer from its relative parent to prevent a loop in the
+    // layer hierarchy. This overrides the RequestedLayerState and leaves
+    // the system in an invalid state. This is always a client error that
+    // needs to be fixed but overriding the state allows us to fail gracefully.
+    void fixRelativeZLoop(uint32_t relativeRootId);
+
     // Destroys RequestedLayerStates that are marked to be destroyed. Invokes all
     // ILifecycleListener callbacks and clears any change flags from previous state
     // updates. This function should be called outside the hot path since it's not
@@ -72,8 +78,8 @@
 
     RequestedLayerState* getLayerFromId(uint32_t);
     std::vector<uint32_t>* getLinkedLayersFromId(uint32_t);
-    void linkLayer(uint32_t layerId, uint32_t layerToLink);
-    void unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer);
+    uint32_t linkLayer(uint32_t layerId, uint32_t layerToLink);
+    uint32_t unlinkLayer(uint32_t layerId, uint32_t linkedLayer);
 
     struct References {
         // Lifetime tied to mLayers
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 45058d9..054382c 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -40,7 +40,7 @@
 }
 
 std::string layerIdToString(uint32_t layerId) {
-    return layerId == UNASSIGNED_LAYER_ID ? std::to_string(layerId) : "none";
+    return layerId == UNASSIGNED_LAYER_ID ? "none" : std::to_string(layerId);
 }
 
 } // namespace
@@ -147,13 +147,17 @@
         static const mat4 identityMatrix = mat4();
         hasColorTransform = colorTransform != identityMatrix;
     }
-    if (clientState.what & layer_state_t::eLayerChanged) {
+    if (clientState.what & (layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged)) {
         changes |= RequestedLayerState::Changes::Z;
     }
     if (clientState.what & layer_state_t::eReparent) {
         changes |= RequestedLayerState::Changes::Parent;
         parentId = getLayerIdFromSurfaceControl(clientState.parentSurfaceControlForChild);
         parentSurfaceControlForChild = nullptr;
+        // Once a layer has be reparented, it cannot be placed at the root. It sounds odd
+        // but thats the existing logic and until we make this behavior more explicit, we need
+        // to maintain this logic.
+        canBeRoot = false;
     }
     if (clientState.what & layer_state_t::eRelativeLayerChanged) {
         changes |= RequestedLayerState::Changes::RelativeParent;
@@ -190,7 +194,6 @@
 
     if (clientState.what & layer_state_t::eBufferChanged) {
         externalTexture = resolvedComposerState.externalTexture;
-        hwcBufferSlot = resolvedComposerState.hwcBufferSlot;
     }
 
     if (clientState.what & layer_state_t::ePositionChanged) {
@@ -254,7 +257,7 @@
             ",relativeParent=" + layerIdToString(relativeParentId) +
             ",isRelativeOf=" + std::to_string(isRelativeOf) +
             ",mirrorId=" + layerIdToString(mirrorId) +
-            ",handleAlive=" + std::to_string(handleAlive);
+            ",handleAlive=" + std::to_string(handleAlive) + ",z=" + std::to_string(z);
 }
 
 std::string RequestedLayerState::getDebugStringShort() const {
@@ -355,4 +358,13 @@
     return Region(win).subtract(exclude).getBounds();
 }
 
+// Returns true if the layer has a relative parent that is not its own parent. This is an input
+// error from the client, and this check allows us to handle it gracefully. If both parentId and
+// relativeParentId is unassigned then the layer does not have a valid relative parent.
+// If the relative parentid is unassigned, the layer will be considered relative but won't be
+// reachable.
+bool RequestedLayerState::hasValidRelativeParent() const {
+    return isRelativeOf && parentId != relativeParentId;
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 0ddf5e2..7849165 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -62,13 +62,14 @@
     std::string getDebugString() const;
     std::string getDebugStringShort() const;
     aidl::android::hardware::graphics::composer3::Composition getCompositionType() const;
+    bool hasValidRelativeParent() const;
 
     // Layer serial number.  This gives layers an explicit ordering, so we
     // have a stable sort order when their layer stack and Z-order are
     // the same.
     const uint32_t id;
     const std::string name;
-    const bool canBeRoot = false;
+    bool canBeRoot = false;
     const uint32_t layerCreationFlags;
     const uint32_t textureName;
     // The owner of the layer. If created from a non system process, it will be the calling uid.
@@ -86,7 +87,6 @@
     ui::Transform requestedTransform;
     std::shared_ptr<FenceTime> acquireFenceTime;
     std::shared_ptr<renderengine::ExternalTexture> externalTexture;
-    int hwcBufferSlot = 0;
 
     // book keeping states
     bool handleAlive = true;
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index 8629671..c2109b3 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -177,7 +177,7 @@
     }
 
     mStalledTransactions.push_back(transactionId);
-    listener->onTransactionQueueStalled(String8(reason.c_str()));
+    listener->onTransactionQueueStalled(reason);
 }
 
 void TransactionHandler::removeFromStalledTransactions(uint64_t id) {
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index a06b870..475ff1b 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -29,6 +29,7 @@
 namespace android {
 
 class TestableSurfaceFlinger;
+using gui::IListenerHash;
 namespace surfaceflinger::frontend {
 
 class TransactionHandler {
diff --git a/services/surfaceflinger/HwcSlotGenerator.cpp b/services/surfaceflinger/HwcSlotGenerator.cpp
deleted file mode 100644
index 939c35b..0000000
--- a/services/surfaceflinger/HwcSlotGenerator.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2022 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "HwcSlotGenerator"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include <gui/BufferQueue.h>
-
-#include "HwcSlotGenerator.h"
-
-namespace android {
-
-HwcSlotGenerator::HwcSlotGenerator() {
-    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
-        mFreeHwcCacheSlots.push(i);
-    }
-}
-
-void HwcSlotGenerator::bufferErased(const client_cache_t& clientCacheId) {
-    std::lock_guard lock(mMutex);
-    if (!clientCacheId.isValid()) {
-        ALOGE("invalid process, failed to erase buffer");
-        return;
-    }
-    eraseBufferLocked(clientCacheId);
-}
-
-int HwcSlotGenerator::getHwcCacheSlot(const client_cache_t& clientCacheId) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    auto itr = mCachedBuffers.find(clientCacheId);
-    if (itr == mCachedBuffers.end()) {
-        return addCachedBuffer(clientCacheId);
-    }
-    auto& [hwcCacheSlot, counter] = itr->second;
-    counter = mCounter++;
-    return hwcCacheSlot;
-}
-
-int HwcSlotGenerator::addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex) {
-    if (!clientCacheId.isValid()) {
-        ALOGE("invalid process, returning invalid slot");
-        return BufferQueue::INVALID_BUFFER_SLOT;
-    }
-
-    ClientCache::getInstance().registerErasedRecipient(clientCacheId,
-                                                       wp<ErasedRecipient>::fromExisting(this));
-
-    int hwcCacheSlot = getFreeHwcCacheSlot();
-    mCachedBuffers[clientCacheId] = {hwcCacheSlot, mCounter++};
-    return hwcCacheSlot;
-}
-
-int HwcSlotGenerator::getFreeHwcCacheSlot() REQUIRES(mMutex) {
-    if (mFreeHwcCacheSlots.empty()) {
-        evictLeastRecentlyUsed();
-    }
-
-    int hwcCacheSlot = mFreeHwcCacheSlots.top();
-    mFreeHwcCacheSlots.pop();
-    return hwcCacheSlot;
-}
-
-void HwcSlotGenerator::evictLeastRecentlyUsed() REQUIRES(mMutex) {
-    uint64_t minCounter = UINT_MAX;
-    client_cache_t minClientCacheId = {};
-    for (const auto& [clientCacheId, slotCounter] : mCachedBuffers) {
-        const auto& [hwcCacheSlot, counter] = slotCounter;
-        if (counter < minCounter) {
-            minCounter = counter;
-            minClientCacheId = clientCacheId;
-        }
-    }
-    eraseBufferLocked(minClientCacheId);
-
-    ClientCache::getInstance().unregisterErasedRecipient(minClientCacheId,
-                                                         wp<ErasedRecipient>::fromExisting(this));
-}
-
-void HwcSlotGenerator::eraseBufferLocked(const client_cache_t& clientCacheId) REQUIRES(mMutex) {
-    auto itr = mCachedBuffers.find(clientCacheId);
-    if (itr == mCachedBuffers.end()) {
-        return;
-    }
-    auto& [hwcCacheSlot, counter] = itr->second;
-
-    // TODO send to hwc cache and resources
-
-    mFreeHwcCacheSlots.push(hwcCacheSlot);
-    mCachedBuffers.erase(clientCacheId);
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/HwcSlotGenerator.h b/services/surfaceflinger/HwcSlotGenerator.h
deleted file mode 100644
index 5a1b6d7..0000000
--- a/services/surfaceflinger/HwcSlotGenerator.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2022 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 <functional>
-#include <mutex>
-#include <stack>
-#include <unordered_map>
-
-#include "ClientCache.h"
-
-namespace android {
-
-class HwcSlotGenerator : public ClientCache::ErasedRecipient {
-public:
-    HwcSlotGenerator();
-    void bufferErased(const client_cache_t& clientCacheId);
-    int getHwcCacheSlot(const client_cache_t& clientCacheId);
-
-private:
-    friend class SlotGenerationTest;
-    int addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex);
-    int getFreeHwcCacheSlot() REQUIRES(mMutex);
-    void evictLeastRecentlyUsed() REQUIRES(mMutex);
-    void eraseBufferLocked(const client_cache_t& clientCacheId) REQUIRES(mMutex);
-
-    struct CachedBufferHash {
-        std::size_t operator()(const client_cache_t& clientCacheId) const {
-            return std::hash<uint64_t>{}(clientCacheId.id);
-        }
-    };
-
-    std::mutex mMutex;
-
-    std::unordered_map<client_cache_t, std::pair<int /*HwcCacheSlot*/, uint64_t /*counter*/>,
-                       CachedBufferHash>
-            mCachedBuffers GUARDED_BY(mMutex);
-    std::stack<int /*HwcCacheSlot*/> mFreeHwcCacheSlots GUARDED_BY(mMutex);
-
-    // The cache increments this counter value when a slot is updated or used.
-    // Used to track the least recently-used buffer
-    uint64_t mCounter = 0;
-};
-} // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 0017af0..b7abd95 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -144,7 +144,6 @@
         mLayerCreationFlags(args.flags),
         mBorderEnabled(false),
         mTextureName(args.textureName),
-        mHwcSlotGenerator(sp<HwcSlotGenerator>::make()),
         mLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
     ALOGV("Creating Layer %s", getDebugName());
 
@@ -573,9 +572,6 @@
     }
 
     snapshot->buffer = getBuffer();
-    snapshot->bufferSlot = (mBufferInfo.mBufferSlot == BufferQueue::INVALID_BUFFER_SLOT)
-            ? 0
-            : mBufferInfo.mBufferSlot;
     snapshot->acquireFence = mBufferInfo.mFence;
     snapshot->frameNumber = mBufferInfo.mFrameNumber;
     snapshot->sidebandStreamHasFrame = false;
@@ -1472,8 +1468,9 @@
     mFrameTracker.getStats(outStats);
 }
 
-void Layer::dumpCallingUidPid(std::string& result) const {
-    StringAppendF(&result, "Layer %s (%s) ownerPid:%d ownerUid:%d\n", getName().c_str(), getType(),
+void Layer::dumpOffscreenDebugInfo(std::string& result) const {
+    std::string hasBuffer = hasBufferOrSidebandStream() ? " (contains buffer)" : "";
+    StringAppendF(&result, "Layer %s%s pid:%d uid:%d\n", getName().c_str(), hasBuffer.c_str(),
                   mOwnerPid, mOwnerUid);
 }
 
@@ -2604,9 +2601,12 @@
         return;
     }
     ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
-    listener->onReleaseBuffer({buffer->getId(), framenumber},
-                              releaseFence ? releaseFence : Fence::NO_FENCE,
-                              currentMaxAcquiredBufferCount);
+    std::optional<os::ParcelFileDescriptor> fenceFd;
+    if (releaseFence) {
+        fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(releaseFence->get())));
+    }
+    listener->onReleaseBuffer({buffer->getId(), framenumber}, fenceFd,
+                              static_cast<int32_t>(currentMaxAcquiredBufferCount));
 }
 
 void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult) {
@@ -2840,7 +2840,7 @@
 bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer,
                       const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime,
                       bool isAutoTimestamp, std::optional<nsecs_t> dequeueTime,
-                      const FrameTimelineInfo& info, int hwcBufferSlot) {
+                      const FrameTimelineInfo& info) {
     ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false"));
     if (!buffer) {
         return false;
@@ -2886,7 +2886,6 @@
     mDrawingState.releaseBufferListener = bufferData.releaseBufferListener;
     mDrawingState.buffer = std::move(buffer);
     mDrawingState.clientCacheId = bufferData.cachedBuffer;
-    mDrawingState.hwcBufferSlot = hwcBufferSlot;
     mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged)
             ? bufferData.acquireFence
             : Fence::NO_FENCE;
@@ -3185,7 +3184,6 @@
     mBufferInfo.mHdrMetadata = mDrawingState.hdrMetadata;
     mBufferInfo.mApi = mDrawingState.api;
     mBufferInfo.mTransformToDisplayInverse = mDrawingState.transformToDisplayInverse;
-    mBufferInfo.mBufferSlot = mDrawingState.hwcBufferSlot;
 }
 
 Rect Layer::computeBufferCrop(const State& s) {
@@ -3968,10 +3966,6 @@
     }
 }
 
-int Layer::getHwcCacheSlot(const client_cache_t& clientCacheId) {
-    return mHwcSlotGenerator->getHwcCacheSlot(clientCacheId);
-}
-
 LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) {
     if (mLayer) {
         mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot);
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index f743896..08a13a3 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -51,7 +51,6 @@
 #include "Client.h"
 #include "DisplayHardware/HWComposer.h"
 #include "FrameTracker.h"
-#include "HwcSlotGenerator.h"
 #include "LayerFE.h"
 #include "LayerVector.h"
 #include "Scheduler/LayerInfo.h"
@@ -146,7 +145,6 @@
         bool transformToDisplayInverse;
         Region transparentRegionHint;
         std::shared_ptr<renderengine::ExternalTexture> buffer;
-        int hwcBufferSlot;
         client_cache_t clientCacheId;
         sp<Fence> acquireFence;
         std::shared_ptr<FenceTime> acquireFenceTime;
@@ -298,8 +296,7 @@
     bool setBuffer(std::shared_ptr<renderengine::ExternalTexture>& /* buffer */,
                    const BufferData& /* bufferData */, nsecs_t /* postTime */,
                    nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
-                   std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/,
-                   int /* hwcBufferSlot */);
+                   std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/);
     bool setDataspace(ui::Dataspace /*dataspace*/);
     bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/);
     bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/);
@@ -500,7 +497,6 @@
 
         std::shared_ptr<renderengine::ExternalTexture> mBuffer;
         uint64_t mFrameNumber;
-        int mBufferSlot{BufferQueue::INVALID_BUFFER_SLOT};
 
         bool mFrameLatencyNeeded{false};
     };
@@ -634,7 +630,7 @@
 
     void miniDump(std::string& result, const DisplayDevice&) const;
     void dumpFrameStats(std::string& result) const;
-    void dumpCallingUidPid(std::string& result) const;
+    void dumpOffscreenDebugInfo(std::string& result) const;
     void clearFrameStats();
     void logFrameStats();
     void getFrameStats(FrameStats* outStats) const;
@@ -813,7 +809,6 @@
     void updateMetadataSnapshot(const LayerMetadata& parentMetadata);
     void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
                                         std::unordered_set<Layer*>& visited);
-    int getHwcCacheSlot(const client_cache_t& clientCacheId);
 
 protected:
     // For unit tests
@@ -1124,8 +1119,6 @@
     // not specify a destination frame.
     ui::Transform mRequestedTransform;
 
-    sp<HwcSlotGenerator> mHwcSlotGenerator;
-
     sp<LayerFE> mLayerFE;
     std::unique_ptr<LayerSnapshot> mSnapshot = std::make_unique<LayerSnapshot>();
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 3e970fd..cdd6044 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -112,6 +112,7 @@
 #include <ui/DisplayIdentification.h>
 #include "BackgroundExecutor.h"
 #include "Client.h"
+#include "ClientCache.h"
 #include "Colorizer.h"
 #include "Display/DisplayMap.h"
 #include "DisplayDevice.h"
@@ -974,17 +975,14 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getStaticDisplayInfo(const sp<IBinder>& displayToken,
-                                              ui::StaticDisplayInfo* info) {
-    if (!displayToken || !info) {
+status_t SurfaceFlinger::getStaticDisplayInfo(int64_t displayId, ui::StaticDisplayInfo* info) {
+    if (!info) {
         return BAD_VALUE;
     }
 
     Mutex::Autolock lock(mStateLock);
-
-    const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
-                                    .transform(&ftl::to_mapped_ref<PhysicalDisplays>)
-                                    .and_then(getDisplayDeviceAndSnapshot());
+    const auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
+    const auto displayOpt = mPhysicalDisplays.get(*id).and_then(getDisplayDeviceAndSnapshot());
 
     if (!displayOpt) {
         return NAME_NOT_FOUND;
@@ -1011,26 +1009,10 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getDynamicDisplayInfo(const sp<IBinder>& displayToken,
-                                               ui::DynamicDisplayInfo* info) {
-    if (!displayToken || !info) {
-        return BAD_VALUE;
-    }
-
-    Mutex::Autolock lock(mStateLock);
-
-    const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
-                                    .transform(&ftl::to_mapped_ref<PhysicalDisplays>)
-                                    .and_then(getDisplayDeviceAndSnapshot());
-    if (!displayOpt) {
-        return NAME_NOT_FOUND;
-    }
-
-    const auto& [display, snapshotRef] = *displayOpt;
-    const auto& snapshot = snapshotRef.get();
-
+void SurfaceFlinger::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*& info,
+                                                   const sp<DisplayDevice>& display,
+                                                   const display::DisplaySnapshot& snapshot) {
     const auto& displayModes = snapshot.displayModes();
-
     info->supportedDisplayModes.clear();
     info->supportedDisplayModes.reserve(displayModes.size());
 
@@ -1104,7 +1086,47 @@
             }
         }
     }
+}
 
+status_t SurfaceFlinger::getDynamicDisplayInfoFromId(int64_t physicalDisplayId,
+                                                     ui::DynamicDisplayInfo* info) {
+    if (!info) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock lock(mStateLock);
+
+    const auto id_ =
+            DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(physicalDisplayId));
+    const auto displayOpt = mPhysicalDisplays.get(*id_).and_then(getDisplayDeviceAndSnapshot());
+
+    if (!displayOpt) {
+        return NAME_NOT_FOUND;
+    }
+
+    const auto& [display, snapshotRef] = *displayOpt;
+    getDynamicDisplayInfoInternal(info, display, snapshotRef.get());
+    return NO_ERROR;
+}
+
+status_t SurfaceFlinger::getDynamicDisplayInfoFromToken(const sp<IBinder>& displayToken,
+                                                        ui::DynamicDisplayInfo* info) {
+    if (!displayToken || !info) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock lock(mStateLock);
+
+    const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
+                                    .transform(&ftl::to_mapped_ref<PhysicalDisplays>)
+                                    .and_then(getDisplayDeviceAndSnapshot());
+
+    if (!displayOpt) {
+        return NAME_NOT_FOUND;
+    }
+
+    const auto& [display, snapshotRef] = *displayOpt;
+    getDynamicDisplayInfoInternal(info, display, snapshotRef.get());
     return NO_ERROR;
 }
 
@@ -1558,7 +1580,8 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::onPullAtom(const int32_t atomId, std::string* pulledData, bool* success) {
+status_t SurfaceFlinger::onPullAtom(const int32_t atomId, std::vector<uint8_t>* pulledData,
+                                    bool* success) {
     *success = mTimeStats->onPullAtom(atomId, pulledData);
     return NO_ERROR;
 }
@@ -2257,6 +2280,8 @@
         });
     }
 
+    refreshArgs.bufferIdsToUncache = std::move(mBufferIdsToUncache);
+
     refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
     for (auto layer : mLayersWithQueuedFrames) {
         if (auto layerFE = layer->getCompositionEngineLayerFE())
@@ -4001,10 +4026,6 @@
                     getExternalTextureFromBufferData(*resolvedState.state.bufferData,
                                                      layerName.c_str(), transactionId);
             mBufferCountTracker.increment(resolvedState.state.surface->localBinder());
-            if (layer) {
-                resolvedState.hwcBufferSlot =
-                        layer->getHwcCacheSlot(resolvedState.state.bufferData->cachedBuffer);
-            }
         }
     }
 
@@ -4036,7 +4057,7 @@
 
 bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo,
                                            std::vector<ResolvedComposerState>& states,
-                                           const Vector<DisplayState>& displays, uint32_t flags,
+                                           Vector<DisplayState>& displays, uint32_t flags,
                                            const InputWindowCommands& inputWindowCommands,
                                            const int64_t desiredPresentTime, bool isAutoTimestamp,
                                            const client_cache_t& uncacheBuffer,
@@ -4045,7 +4066,8 @@
                                            const std::vector<ListenerCallbacks>& listenerCallbacks,
                                            int originPid, int originUid, uint64_t transactionId) {
     uint32_t transactionFlags = 0;
-    for (const DisplayState& display : displays) {
+    for (DisplayState& display : displays) {
+        display.sanitize(permissions);
         transactionFlags |= setDisplayStateLocked(display);
     }
 
@@ -4080,7 +4102,10 @@
     }
 
     if (uncacheBuffer.isValid()) {
-        ClientCache::getInstance().erase(uncacheBuffer);
+        sp<GraphicBuffer> buffer = ClientCache::getInstance().erase(uncacheBuffer);
+        if (buffer != nullptr) {
+            mBufferIdsToUncache.push_back(buffer->getId());
+        }
     }
 
     // If a synchronous transaction is explicitly requested without any changes, force a transaction
@@ -4471,7 +4496,7 @@
     if (what & layer_state_t::eBufferChanged) {
         if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime,
                              desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp,
-                             frameTimelineInfo, composerState.hwcBufferSlot)) {
+                             frameTimelineInfo)) {
             flags |= eTraversalNeeded;
         }
     } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
@@ -5211,7 +5236,7 @@
         std::string result;
         for (Layer* offscreenLayer : mOffscreenLayers) {
             offscreenLayer->traverse(LayerVector::StateSet::Drawing,
-                                     [&](Layer* layer) { layer->dumpCallingUidPid(result); });
+                                     [&](Layer* layer) { layer->dumpOffscreenDebugInfo(result); });
         }
         return result;
     });
@@ -7105,8 +7130,7 @@
                                    layerName, static_cast<uint32_t>(mMaxRenderTargetSize));
         ALOGD("%s", errorMessage.c_str());
         if (bufferData.releaseBufferListener) {
-            bufferData.releaseBufferListener->onTransactionQueueStalled(
-                    String8(errorMessage.c_str()));
+            bufferData.releaseBufferListener->onTransactionQueueStalled(errorMessage);
         }
         return nullptr;
     }
@@ -7124,7 +7148,7 @@
 
             if (bufferData.releaseBufferListener) {
                 bufferData.releaseBufferListener->onTransactionQueueStalled(
-                        String8("Buffer processing hung due to full buffer cache"));
+                        "Buffer processing hung due to full buffer cache");
             }
         }
 
@@ -7298,6 +7322,10 @@
 
 binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId,
                                                             sp<IBinder>* outDisplay) {
+    status_t status = checkAccessPermission();
+    if (status != OK) {
+        return binderStatusFromStatusT(status);
+    }
     const auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
     *outDisplay = mFlinger->getPhysicalDisplayToken(*id);
     return binder::Status::ok();
@@ -7348,11 +7376,12 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(const sp<IBinder>& display,
+binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(int64_t displayId,
                                                          gui::StaticDisplayInfo* outInfo) {
     using Tag = gui::DeviceProductInfo::ManufactureOrModelDate::Tag;
     ui::StaticDisplayInfo info;
-    status_t status = mFlinger->getStaticDisplayInfo(display, &info);
+
+    status_t status = mFlinger->getStaticDisplayInfo(displayId, &info);
     if (status == NO_ERROR) {
         // convert ui::StaticDisplayInfo to gui::StaticDisplayInfo
         outInfo->connectionType = static_cast<gui::DisplayConnectionType>(info.connectionType);
@@ -7391,58 +7420,71 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::getDynamicDisplayInfo(const sp<IBinder>& display,
-                                                          gui::DynamicDisplayInfo* outInfo) {
+void SurfaceComposerAIDL::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo& info,
+                                                        gui::DynamicDisplayInfo*& outInfo) {
+    // convert ui::DynamicDisplayInfo to gui::DynamicDisplayInfo
+    outInfo->supportedDisplayModes.clear();
+    outInfo->supportedDisplayModes.reserve(info.supportedDisplayModes.size());
+    for (const auto& mode : info.supportedDisplayModes) {
+        gui::DisplayMode outMode;
+        outMode.id = mode.id;
+        outMode.resolution.width = mode.resolution.width;
+        outMode.resolution.height = mode.resolution.height;
+        outMode.xDpi = mode.xDpi;
+        outMode.yDpi = mode.yDpi;
+        outMode.refreshRate = mode.refreshRate;
+        outMode.appVsyncOffset = mode.appVsyncOffset;
+        outMode.sfVsyncOffset = mode.sfVsyncOffset;
+        outMode.presentationDeadline = mode.presentationDeadline;
+        outMode.group = mode.group;
+        std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(),
+                       std::back_inserter(outMode.supportedHdrTypes),
+                       [](const ui::Hdr& value) { return static_cast<int32_t>(value); });
+        outInfo->supportedDisplayModes.push_back(outMode);
+    }
+
+    outInfo->activeDisplayModeId = info.activeDisplayModeId;
+    outInfo->renderFrameRate = info.renderFrameRate;
+
+    outInfo->supportedColorModes.clear();
+    outInfo->supportedColorModes.reserve(info.supportedColorModes.size());
+    for (const auto& cmode : info.supportedColorModes) {
+        outInfo->supportedColorModes.push_back(static_cast<int32_t>(cmode));
+    }
+
+    outInfo->activeColorMode = static_cast<int32_t>(info.activeColorMode);
+
+    gui::HdrCapabilities& hdrCapabilities = outInfo->hdrCapabilities;
+    hdrCapabilities.supportedHdrTypes.clear();
+    hdrCapabilities.supportedHdrTypes.reserve(info.hdrCapabilities.getSupportedHdrTypes().size());
+    for (const auto& hdr : info.hdrCapabilities.getSupportedHdrTypes()) {
+        hdrCapabilities.supportedHdrTypes.push_back(static_cast<int32_t>(hdr));
+    }
+    hdrCapabilities.maxLuminance = info.hdrCapabilities.getDesiredMaxLuminance();
+    hdrCapabilities.maxAverageLuminance = info.hdrCapabilities.getDesiredMaxAverageLuminance();
+    hdrCapabilities.minLuminance = info.hdrCapabilities.getDesiredMinLuminance();
+
+    outInfo->autoLowLatencyModeSupported = info.autoLowLatencyModeSupported;
+    outInfo->gameContentTypeSupported = info.gameContentTypeSupported;
+    outInfo->preferredBootDisplayMode = info.preferredBootDisplayMode;
+}
+
+binder::Status SurfaceComposerAIDL::getDynamicDisplayInfoFromToken(
+        const sp<IBinder>& display, gui::DynamicDisplayInfo* outInfo) {
     ui::DynamicDisplayInfo info;
-    status_t status = mFlinger->getDynamicDisplayInfo(display, &info);
+    status_t status = mFlinger->getDynamicDisplayInfoFromToken(display, &info);
     if (status == NO_ERROR) {
-        // convert ui::DynamicDisplayInfo to gui::DynamicDisplayInfo
-        outInfo->supportedDisplayModes.clear();
-        outInfo->supportedDisplayModes.reserve(info.supportedDisplayModes.size());
-        for (const auto& mode : info.supportedDisplayModes) {
-            gui::DisplayMode outMode;
-            outMode.id = mode.id;
-            outMode.resolution.width = mode.resolution.width;
-            outMode.resolution.height = mode.resolution.height;
-            outMode.xDpi = mode.xDpi;
-            outMode.yDpi = mode.yDpi;
-            outMode.refreshRate = mode.refreshRate;
-            outMode.appVsyncOffset = mode.appVsyncOffset;
-            outMode.sfVsyncOffset = mode.sfVsyncOffset;
-            outMode.presentationDeadline = mode.presentationDeadline;
-            outMode.group = mode.group;
-            std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(),
-                           std::back_inserter(outMode.supportedHdrTypes),
-                           [](const ui::Hdr& value) { return static_cast<int32_t>(value); });
+        getDynamicDisplayInfoInternal(info, outInfo);
+    }
+    return binderStatusFromStatusT(status);
+}
 
-            outInfo->supportedDisplayModes.push_back(outMode);
-        }
-
-        outInfo->activeDisplayModeId = info.activeDisplayModeId;
-        outInfo->renderFrameRate = info.renderFrameRate;
-
-        outInfo->supportedColorModes.clear();
-        outInfo->supportedColorModes.reserve(info.supportedColorModes.size());
-        for (const auto& cmode : info.supportedColorModes) {
-            outInfo->supportedColorModes.push_back(static_cast<int32_t>(cmode));
-        }
-
-        outInfo->activeColorMode = static_cast<int32_t>(info.activeColorMode);
-
-        gui::HdrCapabilities& hdrCapabilities = outInfo->hdrCapabilities;
-        hdrCapabilities.supportedHdrTypes.clear();
-        hdrCapabilities.supportedHdrTypes.reserve(
-                info.hdrCapabilities.getSupportedHdrTypes().size());
-        for (const auto& hdr : info.hdrCapabilities.getSupportedHdrTypes()) {
-            hdrCapabilities.supportedHdrTypes.push_back(static_cast<int32_t>(hdr));
-        }
-        hdrCapabilities.maxLuminance = info.hdrCapabilities.getDesiredMaxLuminance();
-        hdrCapabilities.maxAverageLuminance = info.hdrCapabilities.getDesiredMaxAverageLuminance();
-        hdrCapabilities.minLuminance = info.hdrCapabilities.getDesiredMinLuminance();
-
-        outInfo->autoLowLatencyModeSupported = info.autoLowLatencyModeSupported;
-        outInfo->gameContentTypeSupported = info.gameContentTypeSupported;
-        outInfo->preferredBootDisplayMode = info.preferredBootDisplayMode;
+binder::Status SurfaceComposerAIDL::getDynamicDisplayInfoFromId(int64_t displayId,
+                                                                gui::DynamicDisplayInfo* outInfo) {
+    ui::DynamicDisplayInfo info;
+    status_t status = mFlinger->getDynamicDisplayInfoFromId(displayId, &info);
+    if (status == NO_ERROR) {
+        getDynamicDisplayInfoInternal(info, outInfo);
     }
     return binderStatusFromStatusT(status);
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6ddcfbc..c957b67 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -26,6 +26,7 @@
 #include <android/gui/DisplayStatInfo.h>
 #include <android/gui/DisplayState.h>
 #include <android/gui/ISurfaceComposerClient.h>
+#include <android/gui/ITransactionCompletedListener.h>
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
 #include <ftl/future.h>
@@ -34,8 +35,8 @@
 #include <gui/CompositorTiming.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/ISurfaceComposer.h>
-#include <gui/ITransactionCompletedListener.h>
 #include <gui/LayerDebugInfo.h>
+
 #include <gui/LayerState.h>
 #include <layerproto/LayerProtoHeader.h>
 #include <math/mat4.h>
@@ -96,6 +97,7 @@
 #include <unordered_map>
 #include <unordered_set>
 #include <utility>
+#include <vector>
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include "Client.h"
@@ -125,7 +127,9 @@
 using gui::CaptureArgs;
 using gui::DisplayCaptureArgs;
 using gui::IRegionSamplingListener;
+using gui::ITransactionCompletedListener;
 using gui::LayerCaptureArgs;
+
 using gui::ScreenCaptureResults;
 
 namespace frametimeline {
@@ -512,10 +516,13 @@
     status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats);
     status_t getDisplayState(const sp<IBinder>& displayToken, ui::DisplayState*)
             EXCLUDES(mStateLock);
-    status_t getStaticDisplayInfo(const sp<IBinder>& displayToken, ui::StaticDisplayInfo*)
+    status_t getStaticDisplayInfo(int64_t displayId, ui::StaticDisplayInfo*) EXCLUDES(mStateLock);
+    status_t getDynamicDisplayInfoFromId(int64_t displayId, ui::DynamicDisplayInfo*)
             EXCLUDES(mStateLock);
-    status_t getDynamicDisplayInfo(const sp<IBinder>& displayToken, ui::DynamicDisplayInfo*)
-            EXCLUDES(mStateLock);
+    status_t getDynamicDisplayInfoFromToken(const sp<IBinder>& displayToken,
+                                            ui::DynamicDisplayInfo*) EXCLUDES(mStateLock);
+    void getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*&, const sp<DisplayDevice>&,
+                                       const display::DisplaySnapshot&);
     status_t getDisplayNativePrimaries(const sp<IBinder>& displayToken, ui::DisplayPrimaries&);
     status_t setActiveColorMode(const sp<IBinder>& displayToken, ui::ColorMode colorMode);
     status_t getBootDisplayModeSupport(bool* outSupport) const;
@@ -527,7 +534,7 @@
     void setPowerMode(const sp<IBinder>& displayToken, int mode);
     status_t overrideHdrTypes(const sp<IBinder>& displayToken,
                               const std::vector<ui::Hdr>& hdrTypes);
-    status_t onPullAtom(const int32_t atomId, std::string* pulledData, bool* success);
+    status_t onPullAtom(const int32_t atomId, std::vector<uint8_t>* pulledData, bool* success);
     status_t getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers);
     status_t getColorManagement(bool* outGetColorManagement) const;
     status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat,
@@ -702,7 +709,7 @@
      */
     bool applyTransactionState(const FrameTimelineInfo& info,
                                std::vector<ResolvedComposerState>& state,
-                               const Vector<DisplayState>& displays, uint32_t flags,
+                               Vector<DisplayState>& displays, uint32_t flags,
                                const InputWindowCommands& inputWindowCommands,
                                const int64_t desiredPresentTime, bool isAutoTimestamp,
                                const client_cache_t& uncacheBuffer, const int64_t postTime,
@@ -1094,6 +1101,10 @@
     std::atomic<uint32_t> mUniqueTransactionId = 1;
     SortedVector<sp<Layer>> mLayersPendingRemoval;
 
+    // Buffers that have been discarded by clients and need to be evicted from per-layer caches so
+    // the graphics memory can be immediately freed.
+    std::vector<uint64_t> mBufferIdsToUncache;
+
     // global color transform states
     Daltonizer mDaltonizer;
     float mGlobalSaturationFactor = 1.0f;
@@ -1401,10 +1412,12 @@
                                    gui::DisplayStatInfo* outStatInfo) override;
     binder::Status getDisplayState(const sp<IBinder>& display,
                                    gui::DisplayState* outState) override;
-    binder::Status getStaticDisplayInfo(const sp<IBinder>& display,
+    binder::Status getStaticDisplayInfo(int64_t displayId,
                                         gui::StaticDisplayInfo* outInfo) override;
-    binder::Status getDynamicDisplayInfo(const sp<IBinder>& display,
-                                         gui::DynamicDisplayInfo* outInfo) override;
+    binder::Status getDynamicDisplayInfoFromId(int64_t displayId,
+                                               gui::DynamicDisplayInfo* outInfo) override;
+    binder::Status getDynamicDisplayInfoFromToken(const sp<IBinder>& display,
+                                                  gui::DynamicDisplayInfo* outInfo) override;
     binder::Status getDisplayNativePrimaries(const sp<IBinder>& display,
                                              gui::DisplayPrimaries* outPrimaries) override;
     binder::Status setActiveColorMode(const sp<IBinder>& display, int colorMode) override;
@@ -1489,6 +1502,8 @@
     status_t checkAccessPermission(bool usePermissionCache = kUsePermissionCache);
     status_t checkControlDisplayBrightnessPermission();
     status_t checkReadFrameBufferPermission();
+    static void getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo& info,
+                                              gui::DynamicDisplayInfo*& outInfo);
 
 private:
     sp<SurfaceFlinger> mFlinger;
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index e860d88..630cef1 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -27,6 +27,7 @@
 
 #include <algorithm>
 #include <chrono>
+#include <cmath>
 #include <unordered_map>
 
 #include "TimeStats.h"
@@ -90,7 +91,7 @@
 }
 } // namespace
 
-bool TimeStats::populateGlobalAtom(std::string* pulledData) {
+bool TimeStats::populateGlobalAtom(std::vector<uint8_t>* pulledData) {
     std::lock_guard<std::mutex> lock(mMutex);
 
     if (mTimeStats.statsStartLegacy == 0) {
@@ -138,10 +139,11 @@
     // Always clear data.
     clearGlobalLocked();
 
-    return atomList.SerializeToString(pulledData);
+    pulledData->resize(atomList.ByteSizeLong());
+    return atomList.SerializeToArray(pulledData->data(), atomList.ByteSizeLong());
 }
 
-bool TimeStats::populateLayerAtom(std::string* pulledData) {
+bool TimeStats::populateLayerAtom(std::vector<uint8_t>* pulledData) {
     std::lock_guard<std::mutex> lock(mMutex);
 
     std::vector<TimeStatsHelper::TimeStatsLayer*> dumpStats;
@@ -179,6 +181,12 @@
             *atom->mutable_present_to_present() =
                     histogramToProto(present2PresentHist->second.hist, mMaxPulledHistogramBuckets);
         }
+        const auto& present2PresentDeltaHist = layer->deltas.find("present2presentDelta");
+        if (present2PresentDeltaHist != layer->deltas.cend()) {
+            *atom->mutable_present_to_present_delta() =
+                    histogramToProto(present2PresentDeltaHist->second.hist,
+                                     mMaxPulledHistogramBuckets);
+        }
         const auto& post2presentHist = layer->deltas.find("post2present");
         if (post2presentHist != layer->deltas.cend()) {
             *atom->mutable_post_to_present() =
@@ -229,7 +237,8 @@
     // Always clear data.
     clearLayersLocked();
 
-    return atomList.SerializeToString(pulledData);
+    pulledData->resize(atomList.ByteSizeLong());
+    return atomList.SerializeToArray(pulledData->data(), atomList.ByteSizeLong());
 }
 
 TimeStats::TimeStats() : TimeStats(std::nullopt, std::nullopt) {}
@@ -245,7 +254,7 @@
     }
 }
 
-bool TimeStats::onPullAtom(const int atomId, std::string* pulledData) {
+bool TimeStats::onPullAtom(const int atomId, std::vector<uint8_t>* pulledData) {
     bool success = false;
     if (atomId == 10062) { // SURFACEFLINGER_STATS_GLOBAL_INFO
         success = populateGlobalAtom(pulledData);
@@ -450,6 +459,7 @@
 
     LayerRecord& layerRecord = mTimeStatsTracker[layerId];
     TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord;
+    std::optional<int32_t>& prevPresentToPresentMs = layerRecord.prevPresentToPresentMs;
     std::deque<TimeRecord>& timeRecords = layerRecord.timeRecords;
     const int32_t refreshRateBucket =
             clampToNearestBucket(displayRefreshRate, REFRESH_RATE_BUCKET_WIDTH);
@@ -527,6 +537,12 @@
             ALOGV("[%d]-[%" PRIu64 "]-present2present[%d]", layerId,
                   timeRecords[0].frameTime.frameNumber, presentToPresentMs);
             timeStatsLayer.deltas["present2present"].insert(presentToPresentMs);
+            if (prevPresentToPresentMs) {
+                const int32_t presentToPresentDeltaMs =
+                        std::abs(presentToPresentMs - *prevPresentToPresentMs);
+                timeStatsLayer.deltas["present2presentDelta"].insert(presentToPresentDeltaMs);
+            }
+            prevPresentToPresentMs = presentToPresentMs;
         }
         prevTimeRecord = timeRecords[0];
         timeRecords.pop_front();
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 61d7c22..5f58657 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -47,7 +47,7 @@
     virtual ~TimeStats() = default;
 
     // Process a pull request from statsd.
-    virtual bool onPullAtom(const int atomId, std::string* pulledData) = 0;
+    virtual bool onPullAtom(const int atomId, std::vector<uint8_t>* pulledData) = 0;
 
     virtual void parseArgs(bool asProto, const Vector<String16>& args, std::string& result) = 0;
     virtual bool isEnabled() = 0;
@@ -219,6 +219,7 @@
         uint32_t lateAcquireFrames = 0;
         uint32_t badDesiredPresentFrames = 0;
         TimeRecord prevTimeRecord;
+        std::optional<int32_t> prevPresentToPresentMs;
         std::deque<TimeRecord> timeRecords;
     };
 
@@ -244,7 +245,7 @@
     TimeStats(std::optional<size_t> maxPulledLayers,
               std::optional<size_t> maxPulledHistogramBuckets);
 
-    bool onPullAtom(const int atomId, std::string* pulledData) override;
+    bool onPullAtom(const int atomId, std::vector<uint8_t>* pulledData) override;
     void parseArgs(bool asProto, const Vector<String16>& args, std::string& result) override;
     bool isEnabled() override;
     std::string miniDump() override;
@@ -292,8 +293,8 @@
     static const size_t MAX_NUM_TIME_RECORDS = 64;
 
 private:
-    bool populateGlobalAtom(std::string* pulledData);
-    bool populateLayerAtom(std::string* pulledData);
+    bool populateGlobalAtom(std::vector<uint8_t>* pulledData);
+    bool populateLayerAtom(std::vector<uint8_t>* pulledData);
     bool recordReadyLocked(int32_t layerId, TimeRecord* timeRecord);
     void flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate,
                                             std::optional<Fps> renderRate, SetFrameRateVote,
diff --git a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
index d4d444e..8615947 100644
--- a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
+++ b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
@@ -289,7 +289,11 @@
     // Introduced in Android 12.
     optional FrameTimingHistogram app_deadline_misses = 25;
 
-    // Next ID: 27
+    // Variability histogram of present_to_present timings.
+    // Introduced in Android 14.
+    optional FrameTimingHistogram present_to_present_delta = 27;
+
+    // Next ID: 28
 }
 
 /**
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 61ff9bc..c09bcce 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -26,14 +26,27 @@
 #include <unordered_set>
 
 #include <android-base/thread_annotations.h>
+#include <android/gui/ITransactionCompletedListener.h>
+
 #include <binder/IBinder.h>
-#include <ftl/future.h>
-#include <gui/ITransactionCompletedListener.h>
+#include <gui/ListenerStats.h>
+#include <gui/ReleaseCallbackId.h>
+#include <renderengine/RenderEngine.h>
 #include <ui/Fence.h>
 #include <ui/FenceResult.h>
 
 namespace android {
 
+using gui::CallbackId;
+using gui::FrameEventHistoryStats;
+using gui::IListenerHash;
+using gui::ITransactionCompletedListener;
+using gui::JankData;
+using gui::ListenerCallbacks;
+using gui::ListenerStats;
+using gui::ReleaseCallbackId;
+using gui::TransactionStats;
+
 class CallbackHandle : public RefBase {
 public:
     CallbackHandle(const sp<IBinder>& transactionListener, const std::vector<CallbackId>& ids,
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 7bde2c1..380301f 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -27,13 +27,12 @@
 
 namespace android {
 
-// Extends the client side composer state by resolving buffer cache ids.
+// Extends the client side composer state by resolving buffer.
 class ResolvedComposerState : public ComposerState {
 public:
     ResolvedComposerState() = default;
     ResolvedComposerState(ComposerState&& source) { state = std::move(source.state); }
     std::shared_ptr<renderengine::ExternalTexture> externalTexture;
-    int hwcBufferSlot = 0;
 };
 
 struct TransactionState {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index c0a6bdb..81ca659 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -419,7 +419,7 @@
 
     void onPullAtom(FuzzedDataProvider *fdp) {
         const int32_t atomId = fdp->ConsumeIntegral<uint8_t>();
-        std::string pulledData = fdp->ConsumeRandomLengthString().c_str();
+        std::vector<uint8_t> pulledData = fdp->ConsumeRemainingBytes<uint8_t>();
         bool success = fdp->ConsumeBool();
         mFlinger->onPullAtom(atomId, &pulledData, &success);
     }
@@ -491,14 +491,14 @@
         mFlinger->getDisplayState(display, &displayState);
     }
 
-    void getStaticDisplayInfo(sp<IBinder> &display) {
+    void getStaticDisplayInfo(int64_t displayId) {
         ui::StaticDisplayInfo staticDisplayInfo;
-        mFlinger->getStaticDisplayInfo(display, &staticDisplayInfo);
+        mFlinger->getStaticDisplayInfo(displayId, &staticDisplayInfo);
     }
 
-    void getDynamicDisplayInfo(sp<IBinder> &display) {
+    void getDynamicDisplayInfo(int64_t displayId) {
         android::ui::DynamicDisplayInfo dynamicDisplayInfo;
-        mFlinger->getDynamicDisplayInfo(display, &dynamicDisplayInfo);
+        mFlinger->getDynamicDisplayInfoFromId(displayId, &dynamicDisplayInfo);
     }
     void getDisplayNativePrimaries(sp<IBinder> &display) {
         android::ui::DisplayPrimaries displayPrimaries;
@@ -522,7 +522,7 @@
         return ids.front();
     }
 
-    sp<IBinder> fuzzBoot(FuzzedDataProvider *fdp) {
+    std::pair<sp<IBinder>, int64_t> fuzzBoot(FuzzedDataProvider *fdp) {
         mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool());
         const sp<Client> client = sp<Client>::make(mFlinger);
 
@@ -549,13 +549,13 @@
 
         mFlinger->bootFinished();
 
-        return display;
+        return {display, physicalDisplayId.value};
     }
 
     void fuzzSurfaceFlinger(const uint8_t *data, size_t size) {
         FuzzedDataProvider mFdp(data, size);
 
-        sp<IBinder> display = fuzzBoot(&mFdp);
+        auto [display, displayId] = fuzzBoot(&mFdp);
 
         sp<IGraphicBufferProducer> bufferProducer = sp<mock::GraphicBufferProducer>::make();
 
@@ -563,8 +563,8 @@
 
         getDisplayStats(display);
         getDisplayState(display);
-        getStaticDisplayInfo(display);
-        getDynamicDisplayInfo(display);
+        getStaticDisplayInfo(displayId);
+        getDynamicDisplayInfo(displayId);
         getDisplayNativePrimaries(display);
 
         mFlinger->setAutoLowLatencyMode(display, mFdp.ConsumeBool());
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index c5b3fa6..acfc1d4 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -160,7 +160,7 @@
     layer->setBuffer(texture, {} /*bufferData*/, mFdp.ConsumeIntegral<nsecs_t>() /*postTime*/,
                      mFdp.ConsumeIntegral<nsecs_t>() /*desiredTime*/,
                      mFdp.ConsumeBool() /*isAutoTimestamp*/,
-                     {mFdp.ConsumeIntegral<nsecs_t>()} /*dequeue*/, {} /*info*/, 0 /* hwcslot */);
+                     {mFdp.ConsumeIntegral<nsecs_t>()} /*dequeue*/, {} /*info*/);
 
     LayerRenderArea layerArea(*(flinger.flinger()), layer, getFuzzedRect(),
                               {mFdp.ConsumeIntegral<int32_t>(),
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 1676844..4a45eb5 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -83,6 +83,15 @@
         return SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
     }
 
+    static std::optional<uint64_t> getFirstDisplayId() {
+        const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+        if (ids.empty()) {
+            return std::nullopt;
+        }
+
+        return ids.front().value;
+    }
+
     void setupBackgroundSurface() {
         mDisplay = getFirstDisplayToken();
         ASSERT_FALSE(mDisplay == nullptr);
@@ -169,29 +178,25 @@
 TEST_F(CredentialsTest, GetBuiltInDisplayAccessTest) {
     std::function<bool()> condition = [] { return getFirstDisplayToken() != nullptr; };
     // Anyone can access display information.
-    ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, true));
+    ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, false));
 }
 
 TEST_F(CredentialsTest, AllowedGetterMethodsTest) {
     // The following methods are tested with a UID that is not root, graphics,
     // or system, to show that anyone can access them.
     UIDFaker f(AID_BIN);
-    const auto display = getFirstDisplayToken();
-    ASSERT_TRUE(display != nullptr);
-
-    ui::DisplayMode mode;
-    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
-
-    Vector<ui::DisplayMode> modes;
+    const auto id = getFirstDisplayId();
+    ASSERT_TRUE(id);
     ui::DynamicDisplayInfo info;
-    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info));
 }
 
 TEST_F(CredentialsTest, GetDynamicDisplayInfoTest) {
-    const auto display = getFirstDisplayToken();
+    const auto id = getFirstDisplayId();
+    ASSERT_TRUE(id);
     std::function<status_t()> condition = [=]() {
         ui::DynamicDisplayInfo info;
-        return SurfaceComposerClient::getDynamicDisplayInfo(display, &info);
+        return SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info);
     };
     ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, NO_ERROR));
 }
@@ -335,8 +340,10 @@
     status_t error = SurfaceComposerClient::isWideColorDisplay(display, &result);
     ASSERT_EQ(NO_ERROR, error);
     bool hasWideColorMode = false;
+    const auto id = getFirstDisplayId();
+    ASSERT_TRUE(id);
     ui::DynamicDisplayInfo info;
-    SurfaceComposerClient::getDynamicDisplayInfo(display, &info);
+    SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info);
     const auto& colorModes = info.supportedColorModes;
     for (ColorMode colorMode : colorModes) {
         switch (colorMode) {
@@ -363,10 +370,10 @@
 }
 
 TEST_F(CredentialsTest, GetActiveColorModeBasicCorrectness) {
-    const auto display = getFirstDisplayToken();
-    ASSERT_FALSE(display == nullptr);
+    const auto id = getFirstDisplayId();
+    ASSERT_TRUE(id);
     ui::DynamicDisplayInfo info;
-    SurfaceComposerClient::getDynamicDisplayInfo(display, &info);
+    SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info);
     ColorMode colorMode = info.activeColorMode;
     ASSERT_NE(static_cast<ColorMode>(BAD_VALUE), colorMode);
 }
diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
index 10dae46..4be961b 100644
--- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -45,6 +45,7 @@
     void SetUp() override {
         const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
         ASSERT_FALSE(ids.empty());
+        mDisplayId = ids.front().value;
         mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
         status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &mSpecs);
         ASSERT_EQ(res, NO_ERROR);
@@ -58,11 +59,14 @@
     void testSetAllowGroupSwitching(bool allowGroupSwitching);
 
     sp<IBinder> mDisplayToken;
+    uint64_t mDisplayId;
 };
 
 TEST_F(RefreshRateRangeTest, setAllConfigs) {
     ui::DynamicDisplayInfo info;
-    status_t res = SurfaceComposerClient::getDynamicDisplayInfo(mDisplayToken, &info);
+    status_t res =
+            SurfaceComposerClient::getDynamicDisplayInfoFromId(static_cast<int64_t>(mDisplayId),
+                                                               &info);
     const auto& modes = info.supportedDisplayModes;
     ASSERT_EQ(res, NO_ERROR);
     ASSERT_GT(modes.size(), 0);
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 8b0cd78..bf22521 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -70,7 +70,6 @@
         ":libsurfaceflinger_sources",
         "libsurfaceflinger_unittest_main.cpp",
         "AidlPowerHalWrapperTest.cpp",
-        "CachingTest.cpp",
         "CompositionTest.cpp",
         "DispSyncSourceTest.cpp",
         "DisplayIdGeneratorTest.cpp",
@@ -93,6 +92,7 @@
         "LayerHistoryTest.cpp",
         "LayerInfoTest.cpp",
         "LayerMetadataTest.cpp",
+        "LayerHierarchyTest.cpp",
         "LayerLifecycleManagerTest.cpp",
         "LayerTest.cpp",
         "LayerTestUtils.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CachingTest.cpp b/services/surfaceflinger/tests/unittests/CachingTest.cpp
deleted file mode 100644
index c1cbbfb..0000000
--- a/services/surfaceflinger/tests/unittests/CachingTest.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "CachingTest"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <gui/BufferQueue.h>
-
-#include "HwcSlotGenerator.h"
-
-namespace android {
-
-class SlotGenerationTest : public testing::Test {
-protected:
-    sp<HwcSlotGenerator> mHwcSlotGenerator = sp<HwcSlotGenerator>::make();
-    sp<GraphicBuffer> mBuffer1 =
-            sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
-    sp<GraphicBuffer> mBuffer2 =
-            sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
-    sp<GraphicBuffer> mBuffer3 =
-            sp<GraphicBuffer>::make(10u, 10u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
-};
-
-TEST_F(SlotGenerationTest, getHwcCacheSlot_Invalid) {
-    sp<IBinder> binder = sp<BBinder>::make();
-    // test getting invalid client_cache_id
-    client_cache_t id;
-    int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
-    EXPECT_EQ(BufferQueue::INVALID_BUFFER_SLOT, slot);
-}
-
-TEST_F(SlotGenerationTest, getHwcCacheSlot_Basic) {
-    sp<IBinder> binder = sp<BBinder>::make();
-    client_cache_t id;
-    id.token = binder;
-    id.id = 0;
-    int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
-    EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 1, slot);
-
-    client_cache_t idB;
-    idB.token = binder;
-    idB.id = 1;
-    slot = mHwcSlotGenerator->getHwcCacheSlot(idB);
-    EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 2, slot);
-
-    slot = mHwcSlotGenerator->getHwcCacheSlot(idB);
-    EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 2, slot);
-
-    slot = mHwcSlotGenerator->getHwcCacheSlot(id);
-    EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 1, slot);
-}
-
-TEST_F(SlotGenerationTest, getHwcCacheSlot_Reuse) {
-    sp<IBinder> binder = sp<BBinder>::make();
-    std::vector<client_cache_t> ids;
-    uint32_t cacheId = 0;
-    // fill up cache
-    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
-        client_cache_t id;
-        id.token = binder;
-        id.id = cacheId;
-        ids.push_back(id);
-
-        int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
-        EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot);
-        cacheId++;
-    }
-    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
-        int slot = mHwcSlotGenerator->getHwcCacheSlot(ids[static_cast<uint32_t>(i)]);
-        EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot);
-    }
-
-    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
-        client_cache_t id;
-        id.token = binder;
-        id.id = cacheId;
-        int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
-        EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot);
-        cacheId++;
-    }
-}
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
new file mode 100644
index 0000000..8560902
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -0,0 +1,728 @@
+/*
+ * Copyright 2022 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 "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "Layer.h"
+#include "gui/SurfaceComposerClient.h"
+
+#define UPDATE_AND_VERIFY(HIERARCHY)  \
+    ({                                \
+        SCOPED_TRACE("");             \
+        updateAndVerify((HIERARCHY)); \
+    })
+
+namespace android::surfaceflinger::frontend {
+
+namespace {
+LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp<IBinder> parent, wp<IBinder> mirror) {
+    LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
+    args.addToRoot = canBeRoot;
+    args.parentHandle = parent;
+    args.mirrorLayerHandle = mirror;
+    return args;
+}
+} // namespace
+
+// To run test:
+/**
+ mp :libsurfaceflinger_unittest && adb sync; adb shell \
+    /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \
+    --gtest_filter="LayerHierarchyTest.*" --gtest_repeat=100 \
+    --gtest_shuffle \
+    --gtest_brief=1
+*/
+
+class LayerHierarchyTest : public testing::Test {
+protected:
+    LayerHierarchyTest() {
+        // tree with 3 levels of children
+        // ROOT
+        // ├── 1
+        // │   ├── 11
+        // │   │   └── 111
+        // │   ├── 12
+        // │   │   ├── 121
+        // │   │   └── 122
+        // │   │       └── 1221
+        // │   └── 13
+        // └── 2
+
+        createRootLayer(1);
+        createRootLayer(2);
+        createLayer(11, 1);
+        createLayer(12, 1);
+        createLayer(13, 1);
+        createLayer(111, 11);
+        createLayer(121, 12);
+        createLayer(122, 12);
+        createLayer(1221, 122);
+        mLifecycleManager.commitChanges();
+    }
+    std::vector<uint32_t> getTraversalPath(const LayerHierarchy& hierarchy) const {
+        std::vector<uint32_t> layerIds;
+        hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy,
+                                                  const LayerHierarchy::TraversalPath&) -> bool {
+            layerIds.emplace_back(hierarchy.getLayer()->id);
+            return true;
+        });
+        return layerIds;
+    }
+
+    std::vector<uint32_t> getTraversalPathInZOrder(const LayerHierarchy& hierarchy) const {
+        std::vector<uint32_t> layerIds;
+        hierarchy.traverseInZOrder(
+                [&layerIds = layerIds](const LayerHierarchy& hierarchy,
+                                       const LayerHierarchy::TraversalPath&) -> bool {
+                    layerIds.emplace_back(hierarchy.getLayer()->id);
+                    return true;
+                });
+        return layerIds;
+    }
+
+    void createRootLayer(uint32_t id) {
+        sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+        mHandles[id] = handle;
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        layers.emplace_back(std::make_unique<RequestedLayerState>(
+                createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr)));
+        mLifecycleManager.addLayers(std::move(layers));
+    }
+
+    void createLayer(uint32_t id, uint32_t parentId) {
+        sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+        mHandles[id] = handle;
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        layers.emplace_back(std::make_unique<RequestedLayerState>(
+                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/mHandles[parentId],
+                           /*mirror=*/nullptr)));
+        mLifecycleManager.addLayers(std::move(layers));
+    }
+
+    void reparentLayer(uint32_t id, uint32_t newParentId) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        if (newParentId == UNASSIGNED_LAYER_ID) {
+            transactions.back().states.front().state.parentSurfaceControlForChild = nullptr;
+        } else {
+            auto parentHandle = mHandles[newParentId];
+            transactions.back().states.front().state.parentSurfaceControlForChild =
+                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
+                                             static_cast<int32_t>(newParentId), "Test");
+        }
+        transactions.back().states.front().state.what = layer_state_t::eReparent;
+        transactions.back().states.front().state.surface = mHandles[id];
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        if (relativeParentId == UNASSIGNED_LAYER_ID) {
+            transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+        } else {
+            auto parentHandle = mHandles[relativeParentId];
+            transactions.back().states.front().state.relativeLayerSurfaceControl =
+                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), parentHandle,
+                                             static_cast<int32_t>(relativeParentId), "test");
+            transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
+        }
+        transactions.back().states.front().state.surface = mHandles[id];
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) {
+        auto parentHandle = (parent == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[parent];
+        auto mirrorHandle =
+                (layerToMirror == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[layerToMirror];
+
+        sp<LayerHandle> handle = sp<LayerHandle>::make(id);
+        mHandles[id] = handle;
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        layers.emplace_back(std::make_unique<RequestedLayerState>(
+                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentHandle,
+                           /*mirror=*/mHandles[layerToMirror])));
+        mLifecycleManager.addLayers(std::move(layers));
+    }
+
+    void updateBackgroundColor(uint32_t id, half alpha) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+        transactions.back().states.front().state.bgColorAlpha = alpha;
+        transactions.back().states.front().state.surface = mHandles[id];
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({id}); }
+
+    void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
+        if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
+            hierarchyBuilder.update(mLifecycleManager.getLayers(),
+                                    mLifecycleManager.getDestroyedLayers());
+        }
+        mLifecycleManager.commitChanges();
+
+        // rebuild layer hierarchy from scratch and verify that it matches the updated state.
+        LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers());
+        EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()),
+                  getTraversalPath(newBuilder.getHierarchy()));
+        EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()),
+                  getTraversalPathInZOrder(newBuilder.getHierarchy()));
+        EXPECT_FALSE(
+                mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    }
+
+    void setZ(uint32_t id, int32_t z) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+        transactions.back().states.front().state.layerId = static_cast<int32_t>(id);
+        transactions.back().states.front().state.z = z;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+    LayerLifecycleManager mLifecycleManager;
+    std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+};
+
+// reparenting tests
+TEST_F(LayerHierarchyTest, addLayer) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+    createRootLayer(3);
+    createLayer(112, 11);
+    createLayer(12211, 1221);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+    expectedTraversalPath = {1, 11, 111, 112, 12, 121, 122, 1221, 12211, 13, 2, 3};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentLayer) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentLayer(2, 11);
+    reparentLayer(111, 12);
+    reparentLayer(1221, 1);
+    reparentLayer(1221, 13);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 12, 111, 121, 122, 13, 1221};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentLayerToNull) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    reparentLayer(2, UNASSIGNED_LAYER_ID);
+    reparentLayer(11, UNASSIGNED_LAYER_ID);
+    reparentLayer(1221, 13);
+    reparentLayer(1221, UNASSIGNED_LAYER_ID);
+
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 13};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {2, 11, 111, 1221};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentLayerToNullAndDestroyHandles) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentLayer(2, UNASSIGNED_LAYER_ID);
+    reparentLayer(11, UNASSIGNED_LAYER_ID);
+    reparentLayer(1221, UNASSIGNED_LAYER_ID);
+
+    destroyLayerHandle(2);
+    destroyLayerHandle(11);
+    destroyLayerHandle(1221);
+
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 13};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {111};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, destroyHandleThenDestroyParentLayer) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    destroyLayerHandle(111);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    // handle is destroyed but layer is kept alive and reachable by parent
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+    // destroy parent layer and the child gets destroyed
+    reparentLayer(11, UNASSIGNED_LAYER_ID);
+    destroyLayerHandle(11);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, layerSurvivesTemporaryReparentToNull) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentLayer(11, UNASSIGNED_LAYER_ID);
+    reparentLayer(11, 1);
+
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// offscreen tests
+TEST_F(LayerHierarchyTest, layerMovesOnscreen) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    reparentLayer(11, UNASSIGNED_LAYER_ID);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    reparentLayer(11, 1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, addLayerToOffscreenParent) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    reparentLayer(11, UNASSIGNED_LAYER_ID);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    createLayer(112, 11);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {11, 111, 112};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// rel-z tests
+TEST_F(LayerHierarchyTest, setRelativeParent) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentRelativeLayer(11, 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 11, 111};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentFromRelativeParentWithSetLayer) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentRelativeLayer(11, 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    reparentRelativeLayer(11, UNASSIGNED_LAYER_ID);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentToRelativeParent) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentRelativeLayer(11, 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    reparentLayer(11, 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, setParentAsRelativeParent) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentLayer(11, 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    reparentRelativeLayer(11, 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, relativeChildMovesOffscreenIsNotTraversable) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentRelativeLayer(11, 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    reparentLayer(2, UNASSIGNED_LAYER_ID);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {1, 12, 121, 122, 1221, 13};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {2, 11, 111};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// mirror tests
+TEST_F(LayerHierarchyTest, canTraverseMirrorLayer) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1,    11, 111, 12, 121, 122,
+                                                   1221, 13, 14,  11, 111, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, canMirrorOffscreenLayer) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    reparentLayer(11, UNASSIGNED_LAYER_ID);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 14, 11, 111, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {11, 111};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, newChildLayerIsUpdatedInMirrorHierarchy) {
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    mLifecycleManager.commitChanges();
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    createLayer(1111, 111);
+    createLayer(112, 11);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1,    11, 111, 1111, 112, 12,   121, 122,
+                                                   1221, 13, 14,  11,   111, 1111, 112, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// mirror & relatives tests
+TEST_F(LayerHierarchyTest, mirrorWithRelativeOutsideMirrorHierarchy) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentRelativeLayer(111, 12);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12
+    // │   │   ├── 121
+    // │   │   ├── 122
+    // │   │   │   └── 1221
+    // │   │   └ - 111 (relative)
+    // │   ├── 13
+    // │   └── 14
+    // │       └ * 11 (mirroring)
+    // └── 2
+
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1,    11, 111, 12, 111, 121, 122,
+                                                   1221, 13, 14,  11, 111, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    // 111 is not reachable in the mirror
+    expectedTraversalPath = {1, 11, 12, 111, 121, 122, 1221, 13, 14, 11, 2};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, mirrorWithRelativeInsideMirrorHierarchy) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentRelativeLayer(1221, 12);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 12);
+
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12
+    // │   │   ├── 121
+    // │   │   ├── 122
+    // │   │   │   └── 1221
+    // │   │   └ - 1221 (relative)
+    // │   ├── 13
+    // │   └── 14
+    // │       └ * 12 (mirroring)
+    // └── 2
+
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+    std::vector<uint32_t> expectedTraversalPath = {1,  11, 111, 12,  121, 122,  1221, 1221,
+                                                   13, 14, 12,  121, 122, 1221, 1221, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    // relative layer 1221 is traversable in the mirrored hierarchy as well
+    expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 14, 12, 121, 122, 1221, 2};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, childMovesOffscreenWhenRelativeParentDies) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    reparentRelativeLayer(11, 2);
+    reparentLayer(2, UNASSIGNED_LAYER_ID);
+    destroyLayerHandle(2);
+
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {1, 12, 121, 122, 1221, 13};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {11, 111};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+    // remove relative parent so layer becomes onscreen again
+    reparentRelativeLayer(11, UNASSIGNED_LAYER_ID);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, offscreenLayerCannotBeRelativeToOnscreenLayer) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentRelativeLayer(1221, 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    // verify relz path
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 1221};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {1, 11, 111, 12, 121, 122, 13, 2, 1221};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+    // offscreen layer cannot be reached as a relative child
+    reparentLayer(12, UNASSIGNED_LAYER_ID);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    expectedTraversalPath = {1, 11, 111, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {12, 121, 122, 1221};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+    // layer when onscreen can be reached as a relative child again
+    reparentLayer(12, 1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 1221};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {1, 11, 111, 12, 121, 122, 13, 2, 1221};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, backgroundLayersAreBehindParentLayer) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    updateBackgroundColor(1, 0.5);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 1222, 11, 111, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {1222, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// cycle tests
+TEST_F(LayerHierarchyTest, ParentBecomesTheChild) {
+    // remove default hierarchy
+    mLifecycleManager = LayerLifecycleManager();
+    createRootLayer(1);
+    createLayer(11, 1);
+    reparentLayer(1, 11);
+    mLifecycleManager.commitChanges();
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    std::vector<uint32_t> expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, RelativeLoops) {
+    // remove default hierarchy
+    mLifecycleManager = LayerLifecycleManager();
+    createRootLayer(1);
+    createRootLayer(2);
+    createLayer(11, 1);
+    reparentRelativeLayer(11, 2);
+    reparentRelativeLayer(2, 11);
+    mLifecycleManager.commitChanges();
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    // fix loop
+    uint32_t invalidRelativeRoot;
+    bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
+    EXPECT_TRUE(hasRelZLoop);
+    mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
+    hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
+    EXPECT_EQ(invalidRelativeRoot, 11u);
+    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {1};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {11, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, IndirectRelativeLoops) {
+    // remove default hierarchy
+    mLifecycleManager = LayerLifecycleManager();
+    createRootLayer(1);
+    createRootLayer(2);
+    createLayer(11, 1);
+    createLayer(111, 11);
+    createLayer(21, 2);
+    createLayer(22, 2);
+    createLayer(221, 22);
+    reparentRelativeLayer(22, 111);
+    reparentRelativeLayer(11, 221);
+    mLifecycleManager.commitChanges();
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+    // fix loop
+    uint32_t invalidRelativeRoot;
+    bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
+    EXPECT_TRUE(hasRelZLoop);
+    mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
+    hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
+    EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 22, 221, 2, 21, 22, 221};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {1, 2, 21};
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {11, 111, 22, 221};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, ReparentRootLayerToNull) {
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    reparentLayer(1, UNASSIGNED_LAYER_ID);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, AddRemoveLayerInSameTransaction) {
+    // remove default hierarchy
+    mLifecycleManager = LayerLifecycleManager();
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    createRootLayer(1);
+    destroyLayerHandle(1);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// traversal path test
+TEST_F(LayerHierarchyTest, traversalPathId) {
+    setZ(122, -1);
+    LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+    auto checkTraversalPathIdVisitor =
+            [](const LayerHierarchy& hierarchy,
+               const LayerHierarchy::TraversalPath& traversalPath) -> bool {
+        EXPECT_EQ(hierarchy.getLayer()->id, traversalPath.id);
+        return true;
+    };
+    hierarchyBuilder.getHierarchy().traverse(checkTraversalPathIdVisitor);
+    hierarchyBuilder.getHierarchy().traverseInZOrder(checkTraversalPathIdVisitor);
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp
index 11e734a..0e149d2 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp
@@ -61,7 +61,7 @@
 TEST_F(ExcludeDolbyVisionTest, excludesDolbyVisionOnModesHigherThan4k30) {
     injectDisplayModes({mode4k60});
     ui::DynamicDisplayInfo info;
-    mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info);
+    mFlinger.getDynamicDisplayInfoFromToken(mDisplay->getDisplayToken().promote(), &info);
 
     std::vector<ui::DisplayMode> displayModes = info.supportedDisplayModes;
 
@@ -75,7 +75,7 @@
 TEST_F(ExcludeDolbyVisionTest, includesDolbyVisionOnModesLowerThanOrEqualTo4k30) {
     injectDisplayModes({mode1080p60, mode4k30, mode4k30NonStandard});
     ui::DynamicDisplayInfo info;
-    mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info);
+    mFlinger.getDynamicDisplayInfoFromToken(mDisplay->getDisplayToken().promote(), &info);
 
     std::vector<ui::DisplayMode> displayModes = info.supportedDisplayModes;
 
@@ -94,7 +94,7 @@
 TEST_F(ExcludeDolbyVisionTest, 4k30IsNotReportedAsAValidHdrType) {
     injectDisplayModes({mode4k60});
     ui::DynamicDisplayInfo info;
-    mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info);
+    mFlinger.getDynamicDisplayInfoFromToken(mDisplay->getDisplayToken().promote(), &info);
 
     std::vector<ui::Hdr> displayHdrTypes = info.hdrCapabilities.getSupportedHdrTypes();
 
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 7d0b340..2117084 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -487,9 +487,9 @@
 
     void updateLayerMetadataSnapshot() { mFlinger->updateLayerMetadataSnapshot(); }
 
-    void getDynamicDisplayInfo(const sp<IBinder>& displayToken,
-                               ui::DynamicDisplayInfo* dynamicDisplayInfo) {
-        mFlinger->getDynamicDisplayInfo(displayToken, dynamicDisplayInfo);
+    void getDynamicDisplayInfoFromToken(const sp<IBinder>& displayToken,
+                                        ui::DynamicDisplayInfo* dynamicDisplayInfo) {
+        mFlinger->getDynamicDisplayInfoFromToken(displayToken, dynamicDisplayInfo);
     }
 
     /* ------------------------------------------------------------------------
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 1dd4f25..a9ae1d3 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -44,11 +44,14 @@
 namespace {
 
 using testing::_;
+using testing::AllOf;
 using testing::AnyNumber;
 using testing::Contains;
+using testing::ElementsAre;
 using testing::HasSubstr;
 using testing::InSequence;
 using testing::Not;
+using testing::Property;
 using testing::SizeIs;
 using testing::StrEq;
 using testing::UnorderedElementsAre;
@@ -645,7 +648,7 @@
     ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
 
     ASSERT_EQ(1, globalProto.stats_size());
-    const SFTimeStatsLayerProto& layerProto = globalProto.stats().Get(0);
+    const SFTimeStatsLayerProto& layerProto = globalProto.stats(0);
     ASSERT_TRUE(layerProto.has_layer_name());
     EXPECT_EQ(genLayerName(LAYER_ID_0), layerProto.layer_name());
     ASSERT_TRUE(layerProto.has_total_frames());
@@ -653,7 +656,7 @@
     ASSERT_EQ(6, layerProto.deltas_size());
     for (const SFTimeStatsDeltaProto& deltaProto : layerProto.deltas()) {
         ASSERT_EQ(1, deltaProto.histograms_size());
-        const SFTimeStatsHistogramBucketProto& histogramProto = deltaProto.histograms().Get(0);
+        const SFTimeStatsHistogramBucketProto& histogramProto = deltaProto.histograms(0);
         EXPECT_EQ(1, histogramProto.frame_count());
         if ("post2acquire" == deltaProto.delta_name()) {
             EXPECT_EQ(1, histogramProto.time_millis());
@@ -673,6 +676,46 @@
     }
 }
 
+using LayerProto = SFTimeStatsLayerProto;
+using DeltaProto = SFTimeStatsDeltaProto;
+using BucketProto = SFTimeStatsHistogramBucketProto;
+
+TEST_F(TimeStatsTest, canComputeLayerStabilityHistogram) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000); // 0ms delta
+    // Slightly unstable frames
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 5000000); // 1ms delta
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 6000000); // 1ms delta
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    EXPECT_THAT(globalProto.stats(),
+                ElementsAre(AllOf(
+                        Property(&LayerProto::layer_name, genLayerName(LAYER_ID_0)),
+                        Property(&LayerProto::total_frames, 4),
+                        Property(&LayerProto::deltas,
+                                 Contains(AllOf(Property(&DeltaProto::delta_name,
+                                                         "present2presentDelta"),
+                                                Property(&DeltaProto::histograms,
+                                                         UnorderedElementsAre(
+                                                                 AllOf(Property(&BucketProto::
+                                                                                        time_millis,
+                                                                                0),
+                                                                       Property(&BucketProto::
+                                                                                        frame_count,
+                                                                                1)),
+                                                                 AllOf(Property(&BucketProto::
+                                                                                        time_millis,
+                                                                                1),
+                                                                       Property(&BucketProto::
+                                                                                        frame_count,
+                                                                                2))))))))));
+}
+
 TEST_F(TimeStatsTest, canNotInsertInvalidLayerNameTimeStats) {
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
 
@@ -1099,8 +1142,10 @@
                                       kGameMode, JankType::None, DISPLAY_DEADLINE_DELTA,
                                       DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA});
 
+    std::vector<uint8_t> pulledBytes;
+    EXPECT_TRUE(mTimeStats->onPullAtom(10062 /*SURFACEFLINGER_STATS_GLOBAL_INFO*/, &pulledBytes));
     std::string pulledData;
-    EXPECT_TRUE(mTimeStats->onPullAtom(10062 /*SURFACEFLINGER_STATS_GLOBAL_INFO*/, &pulledData));
+    pulledData.assign(pulledBytes.begin(), pulledBytes.end());
 
     android::surfaceflinger::SurfaceflingerStatsGlobalInfoWrapper atomList;
     ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1234,8 +1279,10 @@
                                       GameMode::Standard, JankType::None, DISPLAY_DEADLINE_DELTA,
                                       DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_3MS});
 
+    std::vector<uint8_t> pulledBytes;
+    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
     std::string pulledData;
-    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+    pulledData.assign(pulledBytes.begin(), pulledBytes.end());
 
     SurfaceflingerStatsLayerInfoWrapper atomList;
     ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1322,8 +1369,10 @@
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 4000000, {}, GameMode::Battery);
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 6, 5000000, {}, GameMode::Custom);
 
+    std::vector<uint8_t> pulledBytes;
+    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
     std::string pulledData;
-    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+    pulledData.assign(pulledBytes.begin(), pulledBytes.end());
 
     SurfaceflingerStatsLayerInfoWrapper atomList;
     ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1412,8 +1461,10 @@
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 1, 2000000);
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 2, 3000000);
 
+    std::vector<uint8_t> pulledBytes;
+    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
     std::string pulledData;
-    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+    pulledData.assign(pulledBytes.begin(), pulledBytes.end());
 
     SurfaceflingerStatsLayerInfoWrapper atomList;
     ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1437,8 +1488,10 @@
     mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(3000000));
     mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(5000000));
 
+    std::vector<uint8_t> pulledBytes;
+    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
     std::string pulledData;
-    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+    pulledData.assign(pulledBytes.begin(), pulledBytes.end());
 
     SurfaceflingerStatsLayerInfoWrapper atomList;
     ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1456,8 +1509,10 @@
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 4000000);
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 5000000);
 
+    std::vector<uint8_t> pulledBytes;
+    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
     std::string pulledData;
-    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+    pulledData.assign(pulledBytes.begin(), pulledBytes.end());
 
     SurfaceflingerStatsLayerInfoWrapper atomList;
     ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1476,8 +1531,10 @@
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 2, 3000000);
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 4, 5000000);
 
+    std::vector<uint8_t> pulledBytes;
+    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
     std::string pulledData;
-    EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+    pulledData.assign(pulledBytes.begin(), pulledBytes.end());
 
     SurfaceflingerStatsLayerInfoWrapper atomList;
     ASSERT_TRUE(atomList.ParseFromString(pulledData));
diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
index 09d002f..1173d1c 100644
--- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
@@ -126,7 +126,7 @@
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
         layer->setBuffer(externalTexture, bufferData, postTime, /*desiredPresentTime*/ 30, false,
-                         dequeueTime, FrameTimelineInfo{}, 0);
+                         dequeueTime, FrameTimelineInfo{});
 
         commitTransaction(layer.get());
         nsecs_t latchTime = 25;
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index 7dfbcc0..ae03db4 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -131,7 +131,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
         acquireFence->signalForTest(12);
 
         commitTransaction(layer.get());
@@ -166,7 +166,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -183,7 +183,7 @@
                                                          2ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
         nsecs_t end = systemTime();
         acquireFence2->signalForTest(12);
 
@@ -229,7 +229,7 @@
                                                          1ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
         acquireFence->signalForTest(12);
 
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
@@ -264,7 +264,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
 
@@ -307,7 +307,7 @@
         FrameTimelineInfo ftInfo3;
         ftInfo3.vsyncId = 3;
         ftInfo3.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3, 0);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3);
         EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -352,7 +352,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
 
@@ -367,7 +367,7 @@
                                                          1ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
         acquireFence2->signalForTest(12);
 
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -404,7 +404,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -424,7 +424,7 @@
         FrameTimelineInfo ftInfoInv;
         ftInfoInv.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID;
         ftInfoInv.inputEventId = 0;
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv, 0);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv);
         auto dropEndTime1 = systemTime();
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -445,7 +445,7 @@
         FrameTimelineInfo ftInfo2;
         ftInfo2.vsyncId = 2;
         ftInfo2.inputEventId = 0;
-        layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2, 0);
+        layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2);
         auto dropEndTime2 = systemTime();
         acquireFence3->signalForTest(12);
 
@@ -494,7 +494,7 @@
             FrameTimelineInfo ftInfo;
             ftInfo.vsyncId = 1;
             ftInfo.inputEventId = 0;
-            layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
+            layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
             FrameTimelineInfo ftInfo2;
             ftInfo2.vsyncId = 2;
             ftInfo2.inputEventId = 0;
diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
index 0dee800..86fbadc 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
@@ -27,7 +27,7 @@
     TimeStats();
     ~TimeStats() override;
 
-    MOCK_METHOD2(onPullAtom, bool(const int, std::string*));
+    MOCK_METHOD2(onPullAtom, bool(const int, std::vector<uint8_t>*));
     MOCK_METHOD3(parseArgs, void(bool, const Vector<String16>&, std::string&));
     MOCK_METHOD0(isEnabled, bool());
     MOCK_METHOD0(miniDump, std::string());
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 759149d..c7284ce 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -243,6 +243,11 @@
 // syncronous requests to Surface Flinger):
 enum { MIN_NUM_FRAMES_AGO = 5 };
 
+bool IsSharedPresentMode(VkPresentModeKHR mode) {
+    return mode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR ||
+        mode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR;
+}
+
 struct Swapchain {
     Swapchain(Surface& surface_,
               uint32_t num_images_,
@@ -254,9 +259,7 @@
           pre_transform(pre_transform_),
           frame_timestamps_enabled(false),
           acquire_next_image_timeout(-1),
-          shared(present_mode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR ||
-                 present_mode ==
-                     VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR) {
+          shared(IsSharedPresentMode(present_mode)) {
         ANativeWindow* window = surface.window.get();
         native_window_get_refresh_cycle_duration(
             window,
@@ -1796,6 +1799,173 @@
     return a != VK_SUCCESS ? a : b;
 }
 
+// KHR_incremental_present aspect of QueuePresentKHR
+static void SetSwapchainSurfaceDamage(ANativeWindow *window, const VkPresentRegionKHR *pRegion) {
+    std::vector<android_native_rect_t> rects(pRegion->rectangleCount);
+    for (auto i = 0u; i < pRegion->rectangleCount; i++) {
+        auto const& rect = pRegion->pRectangles[i];
+        if (rect.layer > 0) {
+            ALOGV("vkQueuePresentKHR ignoring invalid layer (%u); using layer 0 instead",
+                rect.layer);
+        }
+
+        rects[i].left = rect.offset.x;
+        rects[i].bottom = rect.offset.y;
+        rects[i].right = rect.offset.x + rect.extent.width;
+        rects[i].top = rect.offset.y + rect.extent.height;
+    }
+    native_window_set_surface_damage(window, rects.data(), rects.size());
+}
+
+// GOOGLE_display_timing aspect of QueuePresentKHR
+static void SetSwapchainFrameTimestamp(Swapchain &swapchain, const VkPresentTimeGOOGLE *pTime) {
+    ANativeWindow *window = swapchain.surface.window.get();
+
+    // We don't know whether the app will actually use GOOGLE_display_timing
+    // with a particular swapchain until QueuePresent; enable it on the BQ
+    // now if needed
+    if (!swapchain.frame_timestamps_enabled) {
+        ALOGV("Calling native_window_enable_frame_timestamps(true)");
+        native_window_enable_frame_timestamps(window, true);
+        swapchain.frame_timestamps_enabled = true;
+    }
+
+    // Record the nativeFrameId so it can be later correlated to
+    // this present.
+    uint64_t nativeFrameId = 0;
+    int err = native_window_get_next_frame_id(
+            window, &nativeFrameId);
+    if (err != android::OK) {
+        ALOGE("Failed to get next native frame ID.");
+    }
+
+    // Add a new timing record with the user's presentID and
+    // the nativeFrameId.
+    swapchain.timing.emplace_back(pTime, nativeFrameId);
+    if (swapchain.timing.size() > MAX_TIMING_INFOS) {
+        swapchain.timing.erase(
+            swapchain.timing.begin(),
+            swapchain.timing.begin() + swapchain.timing.size() - MAX_TIMING_INFOS);
+    }
+    if (pTime->desiredPresentTime) {
+        ALOGV(
+            "Calling native_window_set_buffers_timestamp(%" PRId64 ")",
+            pTime->desiredPresentTime);
+        native_window_set_buffers_timestamp(
+            window,
+            static_cast<int64_t>(pTime->desiredPresentTime));
+    }
+}
+
+static VkResult PresentOneSwapchain(
+        VkQueue queue,
+        Swapchain& swapchain,
+        uint32_t imageIndex,
+        const VkPresentRegionKHR *pRegion,
+        const VkPresentTimeGOOGLE *pTime,
+        uint32_t waitSemaphoreCount,
+        const VkSemaphore *pWaitSemaphores) {
+
+    VkDevice device = GetData(queue).driver_device;
+    const auto& dispatch = GetData(queue).driver;
+
+    Swapchain::Image& img = swapchain.images[imageIndex];
+    VkResult swapchain_result = VK_SUCCESS;
+    VkResult result;
+    int err;
+
+    // XXX: long standing issue: QueueSignalReleaseImageANDROID consumes the
+    // wait semaphores, so this doesn't actually work for the multiple swapchain
+    // case.
+    int fence = -1;
+    result = dispatch.QueueSignalReleaseImageANDROID(
+        queue, waitSemaphoreCount,
+        pWaitSemaphores, img.image, &fence);
+    if (result != VK_SUCCESS) {
+        ALOGE("QueueSignalReleaseImageANDROID failed: %d", result);
+        swapchain_result = result;
+    }
+    if (img.release_fence >= 0)
+        close(img.release_fence);
+    img.release_fence = fence < 0 ? -1 : dup(fence);
+
+    if (swapchain.surface.swapchain_handle == HandleFromSwapchain(&swapchain)) {
+        ANativeWindow* window = swapchain.surface.window.get();
+        if (swapchain_result == VK_SUCCESS) {
+
+            if (pRegion) {
+                SetSwapchainSurfaceDamage(window, pRegion);
+            }
+            if (pTime) {
+                SetSwapchainFrameTimestamp(swapchain, pTime);
+            }
+
+            err = window->queueBuffer(window, img.buffer.get(), fence);
+            // queueBuffer always closes fence, even on error
+            if (err != android::OK) {
+                ALOGE("queueBuffer failed: %s (%d)", strerror(-err), err);
+                swapchain_result = WorstPresentResult(
+                    swapchain_result, VK_ERROR_SURFACE_LOST_KHR);
+            } else {
+                if (img.dequeue_fence >= 0) {
+                    close(img.dequeue_fence);
+                    img.dequeue_fence = -1;
+                }
+                img.dequeued = false;
+            }
+
+            // If the swapchain is in shared mode, immediately dequeue the
+            // buffer so it can be presented again without an intervening
+            // call to AcquireNextImageKHR. We expect to get the same buffer
+            // back from every call to dequeueBuffer in this mode.
+            if (swapchain.shared && swapchain_result == VK_SUCCESS) {
+                ANativeWindowBuffer* buffer;
+                int fence_fd;
+                err = window->dequeueBuffer(window, &buffer, &fence_fd);
+                if (err != android::OK) {
+                    ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err);
+                    swapchain_result = WorstPresentResult(swapchain_result,
+                        VK_ERROR_SURFACE_LOST_KHR);
+                } else if (img.buffer != buffer) {
+                    ALOGE("got wrong image back for shared swapchain");
+                    swapchain_result = WorstPresentResult(swapchain_result,
+                        VK_ERROR_SURFACE_LOST_KHR);
+                } else {
+                    img.dequeue_fence = fence_fd;
+                    img.dequeued = true;
+                }
+            }
+        }
+        if (swapchain_result != VK_SUCCESS) {
+            OrphanSwapchain(device, &swapchain);
+        }
+        // Android will only return VK_SUBOPTIMAL_KHR for vkQueuePresentKHR,
+        // and only when the window's transform/rotation changes.  Extent
+        // changes will not cause VK_SUBOPTIMAL_KHR because of the
+        // application issues that were caused when the following transform
+        // change was added.
+        int window_transform_hint;
+        err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
+                            &window_transform_hint);
+        if (err != android::OK) {
+            ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
+                  strerror(-err), err);
+            swapchain_result = WorstPresentResult(
+                swapchain_result, VK_ERROR_SURFACE_LOST_KHR);
+        }
+        if (swapchain.pre_transform != window_transform_hint) {
+            swapchain_result =
+                WorstPresentResult(swapchain_result, VK_SUBOPTIMAL_KHR);
+        }
+    } else {
+        ReleaseSwapchainImage(device, swapchain.shared, nullptr, fence,
+                              img, true);
+        swapchain_result = VK_ERROR_OUT_OF_DATE_KHR;
+    }
+
+    return swapchain_result;
+}
+
 VKAPI_ATTR
 VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) {
     ATRACE_CALL();
@@ -1804,8 +1974,6 @@
              "vkQueuePresentKHR: invalid VkPresentInfoKHR structure type %d",
              present_info->sType);
 
-    VkDevice device = GetData(queue).driver_device;
-    const auto& dispatch = GetData(queue).driver;
     VkResult final_result = VK_SUCCESS;
 
     // Look at the pNext chain for supported extension structs:
@@ -1841,175 +2009,19 @@
         (present_regions) ? present_regions->pRegions : nullptr;
     const VkPresentTimeGOOGLE* times =
         (present_times) ? present_times->pTimes : nullptr;
-    const VkAllocationCallbacks* allocator = &GetData(device).allocator;
-    android_native_rect_t* rects = nullptr;
-    uint32_t nrects = 0;
 
     for (uint32_t sc = 0; sc < present_info->swapchainCount; sc++) {
         Swapchain& swapchain =
             *SwapchainFromHandle(present_info->pSwapchains[sc]);
-        uint32_t image_idx = present_info->pImageIndices[sc];
-        Swapchain::Image& img = swapchain.images[image_idx];
-        const VkPresentRegionKHR* region =
-            (regions && !swapchain.mailbox_mode) ? &regions[sc] : nullptr;
-        const VkPresentTimeGOOGLE* time = (times) ? &times[sc] : nullptr;
-        VkResult swapchain_result = VK_SUCCESS;
-        VkResult result;
-        int err;
 
-        int fence = -1;
-        result = dispatch.QueueSignalReleaseImageANDROID(
-            queue, present_info->waitSemaphoreCount,
-            present_info->pWaitSemaphores, img.image, &fence);
-        if (result != VK_SUCCESS) {
-            ALOGE("QueueSignalReleaseImageANDROID failed: %d", result);
-            swapchain_result = result;
-        }
-        if (img.release_fence >= 0)
-            close(img.release_fence);
-        img.release_fence = fence < 0 ? -1 : dup(fence);
-
-        if (swapchain.surface.swapchain_handle ==
-            present_info->pSwapchains[sc]) {
-            ANativeWindow* window = swapchain.surface.window.get();
-            if (swapchain_result == VK_SUCCESS) {
-                if (region) {
-                    // Process the incremental-present hint for this swapchain:
-                    uint32_t rcount = region->rectangleCount;
-                    if (rcount > nrects) {
-                        android_native_rect_t* new_rects =
-                            static_cast<android_native_rect_t*>(
-                                allocator->pfnReallocation(
-                                    allocator->pUserData, rects,
-                                    sizeof(android_native_rect_t) * rcount,
-                                    alignof(android_native_rect_t),
-                                    VK_SYSTEM_ALLOCATION_SCOPE_COMMAND));
-                        if (new_rects) {
-                            rects = new_rects;
-                            nrects = rcount;
-                        } else {
-                            rcount = 0;  // Ignore the hint for this swapchain
-                        }
-                    }
-                    for (uint32_t r = 0; r < rcount; ++r) {
-                        if (region->pRectangles[r].layer > 0) {
-                            ALOGV(
-                                "vkQueuePresentKHR ignoring invalid layer "
-                                "(%u); using layer 0 instead",
-                                region->pRectangles[r].layer);
-                        }
-                        int x = region->pRectangles[r].offset.x;
-                        int y = region->pRectangles[r].offset.y;
-                        int width = static_cast<int>(
-                            region->pRectangles[r].extent.width);
-                        int height = static_cast<int>(
-                            region->pRectangles[r].extent.height);
-                        android_native_rect_t* cur_rect = &rects[r];
-                        cur_rect->left = x;
-                        cur_rect->top = y + height;
-                        cur_rect->right = x + width;
-                        cur_rect->bottom = y;
-                    }
-                    native_window_set_surface_damage(window, rects, rcount);
-                }
-                if (time) {
-                    if (!swapchain.frame_timestamps_enabled) {
-                        ALOGV(
-                            "Calling "
-                            "native_window_enable_frame_timestamps(true)");
-                        native_window_enable_frame_timestamps(window, true);
-                        swapchain.frame_timestamps_enabled = true;
-                    }
-
-                    // Record the nativeFrameId so it can be later correlated to
-                    // this present.
-                    uint64_t nativeFrameId = 0;
-                    err = native_window_get_next_frame_id(
-                            window, &nativeFrameId);
-                    if (err != android::OK) {
-                        ALOGE("Failed to get next native frame ID.");
-                    }
-
-                    // Add a new timing record with the user's presentID and
-                    // the nativeFrameId.
-                    swapchain.timing.emplace_back(time, nativeFrameId);
-                    while (swapchain.timing.size() > MAX_TIMING_INFOS) {
-                        swapchain.timing.erase(swapchain.timing.begin());
-                    }
-                    if (time->desiredPresentTime) {
-                        // Set the desiredPresentTime:
-                        ALOGV(
-                            "Calling "
-                            "native_window_set_buffers_timestamp(%" PRId64 ")",
-                            time->desiredPresentTime);
-                        native_window_set_buffers_timestamp(
-                            window,
-                            static_cast<int64_t>(time->desiredPresentTime));
-                    }
-                }
-
-                err = window->queueBuffer(window, img.buffer.get(), fence);
-                // queueBuffer always closes fence, even on error
-                if (err != android::OK) {
-                    ALOGE("queueBuffer failed: %s (%d)", strerror(-err), err);
-                    swapchain_result = WorstPresentResult(
-                        swapchain_result, VK_ERROR_SURFACE_LOST_KHR);
-                } else {
-                    if (img.dequeue_fence >= 0) {
-                        close(img.dequeue_fence);
-                        img.dequeue_fence = -1;
-                    }
-                    img.dequeued = false;
-                }
-
-                // If the swapchain is in shared mode, immediately dequeue the
-                // buffer so it can be presented again without an intervening
-                // call to AcquireNextImageKHR. We expect to get the same buffer
-                // back from every call to dequeueBuffer in this mode.
-                if (swapchain.shared && swapchain_result == VK_SUCCESS) {
-                    ANativeWindowBuffer* buffer;
-                    int fence_fd;
-                    err = window->dequeueBuffer(window, &buffer, &fence_fd);
-                    if (err != android::OK) {
-                        ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err);
-                        swapchain_result = WorstPresentResult(swapchain_result,
-                            VK_ERROR_SURFACE_LOST_KHR);
-                    } else if (img.buffer != buffer) {
-                        ALOGE("got wrong image back for shared swapchain");
-                        swapchain_result = WorstPresentResult(swapchain_result,
-                            VK_ERROR_SURFACE_LOST_KHR);
-                    } else {
-                        img.dequeue_fence = fence_fd;
-                        img.dequeued = true;
-                    }
-                }
-            }
-            if (swapchain_result != VK_SUCCESS) {
-                OrphanSwapchain(device, &swapchain);
-            }
-            // Android will only return VK_SUBOPTIMAL_KHR for vkQueuePresentKHR,
-            // and only when the window's transform/rotation changes.  Extent
-            // changes will not cause VK_SUBOPTIMAL_KHR because of the
-            // application issues that were caused when the following transform
-            // change was added.
-            int window_transform_hint;
-            err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
-                                &window_transform_hint);
-            if (err != android::OK) {
-                ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
-                      strerror(-err), err);
-                swapchain_result = WorstPresentResult(
-                    swapchain_result, VK_ERROR_SURFACE_LOST_KHR);
-            }
-            if (swapchain.pre_transform != window_transform_hint) {
-                swapchain_result =
-                    WorstPresentResult(swapchain_result, VK_SUBOPTIMAL_KHR);
-            }
-        } else {
-            ReleaseSwapchainImage(device, swapchain.shared, nullptr, fence,
-                                  img, true);
-            swapchain_result = VK_ERROR_OUT_OF_DATE_KHR;
-        }
+        VkResult swapchain_result = PresentOneSwapchain(
+            queue,
+            swapchain,
+            present_info->pImageIndices[sc],
+            (regions && !swapchain.mailbox_mode) ? &regions[sc] : nullptr,
+            times ? &times[sc] : nullptr,
+            present_info->waitSemaphoreCount,
+            present_info->pWaitSemaphores);
 
         if (present_info->pResults)
             present_info->pResults[sc] = swapchain_result;
@@ -2017,9 +2029,6 @@
         if (swapchain_result != final_result)
             final_result = WorstPresentResult(final_result, swapchain_result);
     }
-    if (rects) {
-        allocator->pfnFree(allocator->pUserData, rects);
-    }
 
     return final_result;
 }