Merge "[sf] Add readme for surfaceflinger frontend" into udc-dev
diff --git a/include/input/Input.h b/include/input/Input.h
index 1e810b4..fe0c775 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -30,7 +30,6 @@
 #include <stdint.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
-#include <utils/RefBase.h>
 #include <utils/Timers.h>
 #include <array>
 #include <limits>
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index eaf3b5e..d50c5f8 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 
+#include <stdint.h>
+
 __BEGIN_DECLS
 
 /**
@@ -27,7 +29,7 @@
 /**
  * Hints for the session used to signal upcoming changes in the mode or workload.
  */
-enum SessionHint {
+enum SessionHint: int32_t {
     /**
      * This hint indicates a sudden increase in CPU workload intensity. It means
      * that this hint session needs extra CPU resources immediately to meet the
@@ -61,7 +63,7 @@
  * @return 0 on success
  *         EPIPE if communication with the system service has failed.
  */
-int APerformanceHint_sendHint(void* session, int hint);
+int APerformanceHint_sendHint(void* session, SessionHint hint);
 
 /**
  * Return the list of thread ids, this API should only be used for testing only.
diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 84900a7..f2b4a6e 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -190,7 +190,8 @@
         data.writeInt32(apiType);
         data.writeInt32(appUid);
         data.writeInt32(appPid);
-        status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply);
+        status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply,
+                                          IBinder::FLAG_ONEWAY);
         if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
             ALOGD("FGS Logger Transaction failed");
             ALOGD("%d", err);
@@ -205,7 +206,8 @@
         data.writeInt32(apiType);
         data.writeInt32(appUid);
         data.writeInt32(appPid);
-        status_t err = remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply);
+        status_t err =
+                remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
         if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
             ALOGD("FGS Logger Transaction failed");
             ALOGD("%d", err);
@@ -222,7 +224,8 @@
         data.writeInt32(state);
         data.writeInt32(appUid);
         data.writeInt32(appPid);
-        status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply);
+        status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply,
+                                          IBinder::FLAG_ONEWAY);
         if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
             ALOGD("FGS Logger Transaction failed");
             ALOGD("%d", err);
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 80fed98..bf34987 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -217,6 +217,7 @@
         "DebugEGLImageTracker.cpp",
         "DisplayEventDispatcher.cpp",
         "DisplayEventReceiver.cpp",
+        "FenceMonitor.cpp",
         "GLConsumer.cpp",
         "IConsumerListener.cpp",
         "IGraphicBufferConsumer.cpp",
diff --git a/libs/gui/FenceMonitor.cpp b/libs/gui/FenceMonitor.cpp
new file mode 100644
index 0000000..230c81a
--- /dev/null
+++ b/libs/gui/FenceMonitor.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2023 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
+
+#include <gui/FenceMonitor.h>
+#include <gui/TraceUtils.h>
+#include <log/log.h>
+
+#include <thread>
+
+namespace android::gui {
+
+FenceMonitor::FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) {
+    std::thread thread(&FenceMonitor::loop, this);
+    pthread_setname_np(thread.native_handle(), mName);
+    thread.detach();
+}
+
+void FenceMonitor::queueFence(const sp<Fence>& fence) {
+    char message[64];
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
+        snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued);
+        ATRACE_NAME(message);
+        // Need an increment on both to make the trace number correct.
+        mFencesQueued++;
+        mFencesSignaled++;
+        return;
+    }
+    snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued);
+    ATRACE_NAME(message);
+
+    mQueue.push_back(fence);
+    mCondition.notify_one();
+    mFencesQueued++;
+    ATRACE_INT(mName, int32_t(mQueue.size()));
+}
+
+void FenceMonitor::loop() {
+    while (true) {
+        threadLoop();
+    }
+}
+
+void FenceMonitor::threadLoop() {
+    sp<Fence> fence;
+    uint32_t fenceNum;
+    {
+        std::unique_lock<std::mutex> lock(mMutex);
+        while (mQueue.empty()) {
+            mCondition.wait(lock);
+        }
+        fence = mQueue[0];
+        fenceNum = mFencesSignaled;
+    }
+    {
+        char message[64];
+        snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum);
+        ATRACE_NAME(message);
+
+        status_t result = fence->waitForever(message);
+        if (result != OK) {
+            ALOGE("Error waiting for fence: %d", result);
+        }
+    }
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mQueue.pop_front();
+        mFencesSignaled++;
+        ATRACE_INT(mName, int32_t(mQueue.size()));
+    }
+}
+
+} // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index d72f65e..b526a6c 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -59,15 +59,13 @@
 
     virtual ~BpSurfaceComposer();
 
-    status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                                 Vector<ComposerState>& state, const Vector<DisplayState>& displays,
-                                 uint32_t flags, const sp<IBinder>& applyToken,
-                                 InputWindowCommands commands, int64_t desiredPresentTime,
-                                 bool isAutoTimestamp,
-                                 const std::vector<client_cache_t>& uncacheBuffers,
-                                 bool hasListenerCallbacks,
-                                 const std::vector<ListenerCallbacks>& listenerCallbacks,
-                                 uint64_t transactionId) override {
+    status_t setTransactionState(
+            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
+            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp,
+            const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
+            const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
+            const std::vector<uint64_t>& mergedTransactionIds) override {
         Parcel data, reply;
         data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
 
@@ -103,6 +101,11 @@
 
         SAFE_PARCEL(data.writeUint64, transactionId);
 
+        SAFE_PARCEL(data.writeUint32, static_cast<uint32_t>(mergedTransactionIds.size()));
+        for (auto mergedTransactionId : mergedTransactionIds) {
+            SAFE_PARCEL(data.writeUint64, mergedTransactionId);
+        }
+
         if (flags & ISurfaceComposer::eOneWay) {
             return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE,
                     data, &reply, IBinder::FLAG_ONEWAY);
@@ -187,10 +190,16 @@
             uint64_t transactionId = -1;
             SAFE_PARCEL(data.readUint64, &transactionId);
 
+            SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize());
+            std::vector<uint64_t> mergedTransactions(count);
+            for (size_t i = 0; i < count; i++) {
+                SAFE_PARCEL(data.readUint64, &mergedTransactions[i]);
+            }
+
             return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken,
                                        std::move(inputWindowCommands), desiredPresentTime,
                                        isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
-                                       listenerCallbacks, transactionId);
+                                       listenerCallbacks, transactionId, mergedTransactions);
         }
         default: {
             return BBinder::onTransact(code, data, reply, flags);
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index a5cf8d6..ed69100 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -30,6 +30,7 @@
 #include <android/gui/DisplayStatInfo.h>
 #include <android/native_window.h>
 
+#include <gui/FenceMonitor.h>
 #include <gui/TraceUtils.h>
 #include <utils/Log.h>
 #include <utils/NativeHandle.h>
@@ -545,82 +546,6 @@
     return NO_ERROR;
 }
 
-class FenceMonitor {
-public:
-    explicit FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) {
-        std::thread thread(&FenceMonitor::loop, this);
-        pthread_setname_np(thread.native_handle(), mName);
-        thread.detach();
-    }
-
-    void queueFence(const sp<Fence>& fence) {
-        char message[64];
-
-        std::lock_guard<std::mutex> lock(mMutex);
-        if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
-            snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued);
-            ATRACE_NAME(message);
-            // Need an increment on both to make the trace number correct.
-            mFencesQueued++;
-            mFencesSignaled++;
-            return;
-        }
-        snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued);
-        ATRACE_NAME(message);
-
-        mQueue.push_back(fence);
-        mCondition.notify_one();
-        mFencesQueued++;
-        ATRACE_INT(mName, int32_t(mQueue.size()));
-    }
-
-private:
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wmissing-noreturn"
-    void loop() {
-        while (true) {
-            threadLoop();
-        }
-    }
-#pragma clang diagnostic pop
-
-    void threadLoop() {
-        sp<Fence> fence;
-        uint32_t fenceNum;
-        {
-            std::unique_lock<std::mutex> lock(mMutex);
-            while (mQueue.empty()) {
-                mCondition.wait(lock);
-            }
-            fence = mQueue[0];
-            fenceNum = mFencesSignaled;
-        }
-        {
-            char message[64];
-            snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum);
-            ATRACE_NAME(message);
-
-            status_t result = fence->waitForever(message);
-            if (result != OK) {
-                ALOGE("Error waiting for fence: %d", result);
-            }
-        }
-        {
-            std::lock_guard<std::mutex> lock(mMutex);
-            mQueue.pop_front();
-            mFencesSignaled++;
-            ATRACE_INT(mName, int32_t(mQueue.size()));
-        }
-    }
-
-    const char* mName;
-    uint32_t mFencesQueued;
-    uint32_t mFencesSignaled;
-    std::deque<sp<Fence>> mQueue;
-    std::condition_variable mCondition;
-    std::mutex mMutex;
-};
-
 void Surface::getDequeueBufferInputLocked(
         IGraphicBufferProducer::DequeueBufferInput* dequeueInput) {
     LOG_ALWAYS_FATAL_IF(dequeueInput == nullptr, "input is null");
@@ -694,7 +619,7 @@
     ALOGE_IF(fence == nullptr, "Surface::dequeueBuffer: received null Fence! buf=%d", buf);
 
     if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) {
-        static FenceMonitor hwcReleaseThread("HWC release");
+        static gui::FenceMonitor hwcReleaseThread("HWC release");
         hwcReleaseThread.queueFence(fence);
     }
 
@@ -893,7 +818,7 @@
         sp<GraphicBuffer>& gbuf(mSlots[slot].buffer);
 
         if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) {
-            static FenceMonitor hwcReleaseThread("HWC release");
+            static gui::FenceMonitor hwcReleaseThread("HWC release");
             hwcReleaseThread.queueFence(output.fence);
         }
 
@@ -1163,7 +1088,7 @@
     mQueueBufferCondition.broadcast();
 
     if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) {
-        static FenceMonitor gpuCompletionThread("GPU completion");
+        static gui::FenceMonitor gpuCompletionThread("GPU completion");
         gpuCompletionThread.queueFence(fence);
     }
 }
@@ -1871,12 +1796,14 @@
     auto frameTimelineVsyncId = static_cast<int64_t>(va_arg(args, int64_t));
     auto inputEventId = static_cast<int32_t>(va_arg(args, int32_t));
     auto startTimeNanos = static_cast<int64_t>(va_arg(args, int64_t));
+    auto useForRefreshRateSelection = static_cast<bool>(va_arg(args, int32_t));
 
     ALOGV("Surface::%s", __func__);
     FrameTimelineInfo ftlInfo;
     ftlInfo.vsyncId = frameTimelineVsyncId;
     ftlInfo.inputEventId = inputEventId;
     ftlInfo.startTimeNanos = startTimeNanos;
+    ftlInfo.useForRefreshRateSelection = useForRefreshRateSelection;
     return setFrameTimelineInfo(frameNumber, ftlInfo);
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 1b13ec1..0fda358 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -827,6 +827,15 @@
         SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id);
     }
 
+    count = static_cast<size_t>(parcel->readUint32());
+    if (count > parcel->dataSize()) {
+        return BAD_VALUE;
+    }
+    std::vector<uint64_t> mergedTransactionIds(count);
+    for (size_t i = 0; i < count; i++) {
+        SAFE_PARCEL(parcel->readUint64, &mergedTransactionIds[i]);
+    }
+
     // Parsing was successful. Update the object.
     mId = transactionId;
     mTransactionNestCount = transactionNestCount;
@@ -842,6 +851,7 @@
     mInputWindowCommands = inputWindowCommands;
     mApplyToken = applyToken;
     mUncacheBuffers = std::move(uncacheBuffers);
+    mMergedTransactionIds = std::move(mergedTransactionIds);
     return NO_ERROR;
 }
 
@@ -900,6 +910,11 @@
         SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id);
     }
 
+    SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(mMergedTransactionIds.size()));
+    for (auto mergedTransactionId : mMergedTransactionIds) {
+        SAFE_PARCEL(parcel->writeUint64, mergedTransactionId);
+    }
+
     return NO_ERROR;
 }
 
@@ -924,6 +939,22 @@
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Transaction&& other) {
+    while (mMergedTransactionIds.size() + other.mMergedTransactionIds.size() >
+                   MAX_MERGE_HISTORY_LENGTH - 1 &&
+           mMergedTransactionIds.size() > 0) {
+        mMergedTransactionIds.pop_back();
+    }
+    if (other.mMergedTransactionIds.size() == MAX_MERGE_HISTORY_LENGTH) {
+        mMergedTransactionIds.insert(mMergedTransactionIds.begin(),
+                                     other.mMergedTransactionIds.begin(),
+                                     other.mMergedTransactionIds.end() - 1);
+    } else if (other.mMergedTransactionIds.size() > 0u) {
+        mMergedTransactionIds.insert(mMergedTransactionIds.begin(),
+                                     other.mMergedTransactionIds.begin(),
+                                     other.mMergedTransactionIds.end());
+    }
+    mMergedTransactionIds.insert(mMergedTransactionIds.begin(), other.mId);
+
     for (auto const& [handle, composerState] : other.mComposerStates) {
         if (mComposerStates.count(handle) == 0) {
             mComposerStates[handle] = composerState;
@@ -998,12 +1029,17 @@
     mIsAutoTimestamp = true;
     clearFrameTimelineInfo(mFrameTimelineInfo);
     mApplyToken = nullptr;
+    mMergedTransactionIds.clear();
 }
 
 uint64_t SurfaceComposerClient::Transaction::getId() {
     return mId;
 }
 
+std::vector<uint64_t> SurfaceComposerClient::Transaction::getMergedTransactionIds() {
+    return mMergedTransactionIds;
+}
+
 void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) {
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
 
@@ -1014,7 +1050,7 @@
     status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
                                               ISurfaceComposer::eOneWay,
                                               Transaction::getDefaultApplyToken(), {}, systemTime(),
-                                              true, {uncacheBuffer}, false, {}, generateId());
+                                              true, {uncacheBuffer}, false, {}, generateId(), {});
     if (status != NO_ERROR) {
         ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s",
                         strerror(-status));
@@ -1189,7 +1225,8 @@
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
     sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
                             mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,
-                            mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId);
+                            mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId,
+                            mMergedTransactionIds);
     mId = generateId();
 
     // Clear the current states and flags
@@ -2245,11 +2282,13 @@
             t.vsyncId = other.vsyncId;
             t.inputEventId = other.inputEventId;
             t.startTimeNanos = other.startTimeNanos;
+            t.useForRefreshRateSelection = other.useForRefreshRateSelection;
         }
     } else if (t.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
         t.vsyncId = other.vsyncId;
         t.inputEventId = other.inputEventId;
         t.startTimeNanos = other.startTimeNanos;
+        t.useForRefreshRateSelection = other.useForRefreshRateSelection;
     }
 }
 
@@ -2258,6 +2297,7 @@
     t.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID;
     t.inputEventId = os::IInputConstants::INVALID_INPUT_EVENT_ID;
     t.startTimeNanos = 0;
+    t.useForRefreshRateSelection = false;
 }
 
 SurfaceComposerClient::Transaction&
diff --git a/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl
index 6ffe466..6a86c6a 100644
--- a/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl
+++ b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl
@@ -33,4 +33,8 @@
 
     // The current time in nanoseconds the application started to render the frame.
     long startTimeNanos = 0;
+
+    // Whether this vsyncId should be used to heuristically select the display refresh rate
+    // TODO(b/281695725): Clean this up once TextureView use setFrameRate API
+    boolean useForRefreshRateSelection = false;
 }
diff --git a/libs/gui/include/gui/FenceMonitor.h b/libs/gui/include/gui/FenceMonitor.h
new file mode 100644
index 0000000..62cedde
--- /dev/null
+++ b/libs/gui/include/gui/FenceMonitor.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 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 <cstdint>
+#include <deque>
+#include <mutex>
+
+#include <ui/Fence.h>
+
+namespace android::gui {
+
+class FenceMonitor {
+public:
+    explicit FenceMonitor(const char* name);
+    void queueFence(const sp<Fence>& fence);
+
+private:
+    void loop();
+    void threadLoop();
+
+    const char* mName;
+    uint32_t mFencesQueued;
+    uint32_t mFencesSignaled;
+    std::deque<sp<Fence>> mQueue;
+    std::condition_variable mCondition;
+    std::mutex mMutex;
+};
+
+} // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index bd21851..7c150d5 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -116,7 +116,7 @@
             InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffer,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
-            uint64_t transactionId) = 0;
+            uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 8d2cdaf..fb57f63 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -419,6 +419,11 @@
                 mListenerCallbacks;
         std::vector<client_cache_t> mUncacheBuffers;
 
+        // We keep track of the last MAX_MERGE_HISTORY_LENGTH merged transaction ids.
+        // Ordered most recently merged to least recently merged.
+        static const size_t MAX_MERGE_HISTORY_LENGTH = 10u;
+        std::vector<uint64_t> mMergedTransactionIds;
+
         uint64_t mId;
 
         uint32_t mTransactionNestCount = 0;
@@ -482,6 +487,8 @@
         // The id is updated every time the transaction is applied.
         uint64_t getId();
 
+        std::vector<uint64_t> getMergedTransactionIds();
+
         status_t apply(bool synchronous = false, bool oneWay = false);
         // Merge another transaction in to this one, clearing other
         // as if it had been applied.
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 5bc6904..096a43c 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -695,16 +695,14 @@
         mSupportsPresent = supportsPresent;
     }
 
-    status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/,
-                                 Vector<ComposerState>& /*state*/,
-                                 const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
-                                 const sp<IBinder>& /*applyToken*/,
-                                 InputWindowCommands /*inputWindowCommands*/,
-                                 int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
-                                 const std::vector<client_cache_t>& /*cachedBuffer*/,
-                                 bool /*hasListenerCallbacks*/,
-                                 const std::vector<ListenerCallbacks>& /*listenerCallbacks*/,
-                                 uint64_t /*transactionId*/) override {
+    status_t setTransactionState(
+            const FrameTimelineInfo& /*frameTimelineInfo*/, Vector<ComposerState>& /*state*/,
+            const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
+            const sp<IBinder>& /*applyToken*/, InputWindowCommands /*inputWindowCommands*/,
+            int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
+            const std::vector<client_cache_t>& /*cachedBuffer*/, bool /*hasListenerCallbacks*/,
+            const std::vector<ListenerCallbacks>& /*listenerCallbacks*/, uint64_t /*transactionId*/,
+            const std::vector<uint64_t>& /*mergedTransactionIds*/) override {
         return NO_ERROR;
     }
 
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index af9ce3a..9a459b1 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -49,11 +49,10 @@
     std::chrono::seconds seconds = std::chrono::duration_cast<std::chrono::seconds>(eventTime);
     std::chrono::microseconds microseconds =
             std::chrono::duration_cast<std::chrono::microseconds>(eventTime - seconds);
-    struct input_event ev = {.type = type,
-                             .code = code,
-                             .value = value,
-                             .input_event_sec = static_cast<time_t>(seconds.count()),
-                             .input_event_usec = static_cast<suseconds_t>(microseconds.count())};
+    struct input_event ev = {.type = type, .code = code, .value = value};
+    ev.input_event_sec = static_cast<decltype(ev.input_event_sec)>(seconds.count());
+    ev.input_event_usec = static_cast<decltype(ev.input_event_usec)>(microseconds.count());
+
     return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev);
 }
 
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 6c54635..0fee3c1 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1066,13 +1066,12 @@
                            (int)compatibility, (int)changeFrameRateStrategy);
 }
 
-static inline int native_window_set_frame_timeline_info(struct ANativeWindow* window,
-                                                        uint64_t frameNumber,
-                                                        int64_t frameTimelineVsyncId,
-                                                        int32_t inputEventId,
-                                                        int64_t startTimeNanos) {
+static inline int native_window_set_frame_timeline_info(
+        struct ANativeWindow* window, uint64_t frameNumber, int64_t frameTimelineVsyncId,
+        int32_t inputEventId, int64_t startTimeNanos, int32_t useForRefreshRateSelection) {
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameNumber,
-                           frameTimelineVsyncId, inputEventId, startTimeNanos);
+                           frameTimelineVsyncId, inputEventId, startTimeNanos,
+                           useForRefreshRateSelection);
 }
 
 // ------------------------------------------------------------------------------------------------
diff --git a/libs/permission/AppOpsManager.cpp b/libs/permission/AppOpsManager.cpp
index baa9d75..6959274 100644
--- a/libs/permission/AppOpsManager.cpp
+++ b/libs/permission/AppOpsManager.cpp
@@ -146,6 +146,14 @@
     }
 }
 
+void AppOpsManager::startWatchingMode(int32_t op, const String16& packageName, int32_t flags,
+        const sp<IAppOpsCallback>& callback) {
+    sp<IAppOpsService> service = getService();
+    if (service != nullptr) {
+        service->startWatchingModeWithFlags(op, packageName, flags, callback);
+    }
+}
+
 void AppOpsManager::stopWatchingMode(const sp<IAppOpsCallback>& callback) {
     sp<IAppOpsService> service = getService();
     if (service != nullptr) {
diff --git a/libs/permission/IAppOpsService.cpp b/libs/permission/IAppOpsService.cpp
index d59f445..7f235a4 100644
--- a/libs/permission/IAppOpsService.cpp
+++ b/libs/permission/IAppOpsService.cpp
@@ -166,6 +166,17 @@
         }
         return reply.readBool();
     }
+
+    virtual void startWatchingModeWithFlags(int32_t op, const String16& packageName,
+        int32_t flags, const sp<IAppOpsCallback>& callback) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IAppOpsService::getInterfaceDescriptor());
+        data.writeInt32(op);
+        data.writeString16(packageName);
+        data.writeInt32(flags);
+        data.writeStrongBinder(IInterface::asBinder(callback));
+        remote()->transact(START_WATCHING_MODE_WITH_FLAGS_TRANSACTION, data, &reply);
+    }
 };
 
 IMPLEMENT_META_INTERFACE(AppOpsService, "com.android.internal.app.IAppOpsService")
diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h
index abcd527..243532b 100644
--- a/libs/permission/include/binder/AppOpsManager.h
+++ b/libs/permission/include/binder/AppOpsManager.h
@@ -151,6 +151,10 @@
         _NUM_OP = 117
     };
 
+    enum {
+        WATCH_FOREGROUND_CHANGES = 1 << 0
+    };
+
     AppOpsManager();
 
     int32_t checkOp(int32_t op, int32_t uid, const String16& callingPackage);
@@ -174,6 +178,8 @@
             const std::optional<String16>& attributionTag);
     void startWatchingMode(int32_t op, const String16& packageName,
             const sp<IAppOpsCallback>& callback);
+    void startWatchingMode(int32_t op, const String16& packageName, int32_t flags,
+            const sp<IAppOpsCallback>& callback);
     void stopWatchingMode(const sp<IAppOpsCallback>& callback);
     int32_t permissionToOpCode(const String16& permission);
     void setCameraAudioRestriction(int32_t mode);
diff --git a/libs/permission/include/binder/IAppOpsService.h b/libs/permission/include/binder/IAppOpsService.h
index 22f056b..918fcdb 100644
--- a/libs/permission/include/binder/IAppOpsService.h
+++ b/libs/permission/include/binder/IAppOpsService.h
@@ -52,6 +52,8 @@
             const String16& packageName) = 0;
     virtual void setCameraAudioRestriction(int32_t mode) = 0;
     virtual bool shouldCollectNotes(int32_t opCode) = 0;
+    virtual void startWatchingModeWithFlags(int32_t op, const String16& packageName,
+            int32_t flags, const sp<IAppOpsCallback>& callback) = 0;
 
     enum {
         CHECK_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
@@ -64,6 +66,7 @@
         CHECK_AUDIO_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+7,
         SHOULD_COLLECT_NOTES_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+8,
         SET_CAMERA_AUDIO_RESTRICTION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+9,
+        START_WATCHING_MODE_WITH_FLAGS_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+10,
     };
 
     enum {
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index cfea85f..5854135 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -39,10 +39,10 @@
 #include <SkPath.h>
 #include <SkPoint.h>
 #include <SkPoint3.h>
+#include <SkRRect.h>
 #include <SkRect.h>
 #include <SkRefCnt.h>
 #include <SkRegion.h>
-#include <SkRRect.h>
 #include <SkRuntimeEffect.h>
 #include <SkSamplingOptions.h>
 #include <SkScalar.h>
@@ -51,9 +51,11 @@
 #include <SkString.h>
 #include <SkSurface.h>
 #include <SkTileMode.h>
-#include <src/core/SkTraceEventCommon.h>
 #include <android-base/stringprintf.h>
+#include <gui/FenceMonitor.h>
 #include <gui/TraceUtils.h>
+#include <pthread.h>
+#include <src/core/SkTraceEventCommon.h>
 #include <sync/sync.h>
 #include <ui/BlurRegion.h>
 #include <ui/DataspaceUtils.h>
@@ -63,6 +65,7 @@
 
 #include <cmath>
 #include <cstdint>
+#include <deque>
 #include <memory>
 #include <numeric>
 
@@ -229,7 +232,6 @@
 static inline SkPoint3 getSkPoint3(const android::vec3& vector) {
     return SkPoint3::Make(vector.x, vector.y, vector.z);
 }
-
 } // namespace
 
 namespace android {
@@ -1134,8 +1136,13 @@
         activeSurface->flush();
     }
 
-    base::unique_fd drawFence = flushAndSubmit(grContext);
-    resultPromise->set_value(sp<Fence>::make(std::move(drawFence)));
+    auto drawFence = sp<Fence>::make(flushAndSubmit(grContext));
+
+    if (ATRACE_ENABLED()) {
+        static gui::FenceMonitor sMonitor("RE Completion");
+        sMonitor.queueFence(drawFence);
+    }
+    resultPromise->set_value(std::move(drawFence));
 }
 
 size_t SkiaRenderEngine::getMaxTextureSize() const {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index f3ada8e..9125fe4 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -681,6 +681,25 @@
     return left;
 }
 
+// Filter windows in a TouchState and targets in a vector to remove untrusted windows/targets from
+// both.
+void filterUntrustedTargets(TouchState& touchState, std::vector<InputTarget>& targets) {
+    std::erase_if(touchState.windows, [&](const TouchedWindow& window) {
+        if (!window.windowHandle->getInfo()->inputConfig.test(
+                    WindowInfo::InputConfig::TRUSTED_OVERLAY)) {
+            // In addition to TouchState, erase this window from the input targets! We don't have a
+            // good way to do this today except by adding a nested loop.
+            // TODO(b/282025641): simplify this code once InputTargets are being identified
+            // separately from TouchedWindows.
+            std::erase_if(targets, [&](const InputTarget& target) {
+                return target.inputChannel->getConnectionToken() == window.windowHandle->getToken();
+            });
+            return true;
+        }
+        return false;
+    });
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -2588,6 +2607,14 @@
         }
     }
 
+    // If this is a touchpad navigation gesture, it needs to only be sent to trusted targets, as we
+    // only want the system UI to handle these gestures.
+    const bool isTouchpadNavGesture = isFromSource(entry.source, AINPUT_SOURCE_MOUSE) &&
+            entry.classification == MotionClassification::MULTI_FINGER_SWIPE;
+    if (isTouchpadNavGesture) {
+        filterUntrustedTargets(/* byref */ tempTouchState, /* byref */ targets);
+    }
+
     // Output targets from the touch state.
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
         if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) {
@@ -2595,6 +2622,7 @@
             // Do not send this event to those windows.
             continue;
         }
+
         addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
                               touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
                               targets);
@@ -2783,6 +2811,30 @@
     }
 }
 
+std::optional<InputTarget> InputDispatcher::createInputTargetLocked(
+        const sp<android::gui::WindowInfoHandle>& windowHandle,
+        ftl::Flags<InputTarget::Flags> targetFlags,
+        std::optional<nsecs_t> firstDownTimeInTarget) const {
+    std::shared_ptr<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
+    if (inputChannel == nullptr) {
+        ALOGW("Not creating InputTarget for %s, no input channel", windowHandle->getName().c_str());
+        return {};
+    }
+    InputTarget inputTarget;
+    inputTarget.inputChannel = inputChannel;
+    inputTarget.flags = targetFlags;
+    inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor;
+    inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
+    const auto& displayInfoIt = mDisplayInfos.find(windowHandle->getInfo()->displayId);
+    if (displayInfoIt != mDisplayInfos.end()) {
+        inputTarget.displayTransform = displayInfoIt->second.transform;
+    } else {
+        // DisplayInfo not found for this window on display windowInfo->displayId.
+        // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed.
+    }
+    return inputTarget;
+}
+
 void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
                                             ftl::Flags<InputTarget::Flags> targetFlags,
                                             std::bitset<MAX_POINTER_ID + 1> pointerIds,
@@ -2798,25 +2850,12 @@
     const WindowInfo* windowInfo = windowHandle->getInfo();
 
     if (it == inputTargets.end()) {
-        InputTarget inputTarget;
-        std::shared_ptr<InputChannel> inputChannel =
-                getInputChannelLocked(windowHandle->getToken());
-        if (inputChannel == nullptr) {
-            ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
+        std::optional<InputTarget> target =
+                createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
+        if (!target) {
             return;
         }
-        inputTarget.inputChannel = inputChannel;
-        inputTarget.flags = targetFlags;
-        inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
-        inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
-        const auto& displayInfoIt = mDisplayInfos.find(windowInfo->displayId);
-        if (displayInfoIt != mDisplayInfos.end()) {
-            inputTarget.displayTransform = displayInfoIt->second.transform;
-        } else {
-            // DisplayInfo not found for this window on display windowInfo->displayId.
-            // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed.
-        }
-        inputTargets.push_back(inputTarget);
+        inputTargets.push_back(*target);
         it = inputTargets.end() - 1;
     }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 9b12f2f..0e9cfef 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -556,6 +556,10 @@
     std::vector<Monitor> selectResponsiveMonitorsLocked(
             const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
 
+    std::optional<InputTarget> createInputTargetLocked(
+            const sp<android::gui::WindowInfoHandle>& windowHandle,
+            ftl::Flags<InputTarget::Flags> targetFlags,
+            std::optional<nsecs_t> firstDownTimeInTarget) const REQUIRES(mLock);
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                ftl::Flags<InputTarget::Flags> targetFlags,
                                std::bitset<MAX_POINTER_ID + 1> pointerIds,
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 0eb4ad2..0354164 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -1072,16 +1072,8 @@
     Device* device = getDeviceLocked(deviceId);
     if (device != nullptr && device->keyMap.haveKeyLayout()) {
         for (size_t codeIndex = 0; codeIndex < keyCodes.size(); codeIndex++) {
-            std::vector<int32_t> scanCodes =
-                    device->keyMap.keyLayoutMap->findScanCodesForKey(keyCodes[codeIndex]);
-
-            // check the possible scan codes identified by the layout map against the
-            // map of codes actually emitted by the driver
-            for (const int32_t scanCode : scanCodes) {
-                if (device->keyBitmask.test(scanCode)) {
-                    outFlags[codeIndex] = 1;
-                    break;
-                }
+            if (device->hasKeycodeLocked(keyCodes[codeIndex])) {
+                outFlags[codeIndex] = 1;
             }
         }
         return true;
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 721bb0a..c8c5115 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -446,17 +446,17 @@
 
     // Switch-like devices.
     if (classes.test(InputDeviceClass::SWITCH)) {
-        mappers.push_back(std::make_unique<SwitchInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(createInputMapper<SwitchInputMapper>(contextPtr, readerConfig));
     }
 
     // Scroll wheel-like devices.
     if (classes.test(InputDeviceClass::ROTARY_ENCODER)) {
-        mappers.push_back(std::make_unique<RotaryEncoderInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(createInputMapper<RotaryEncoderInputMapper>(contextPtr, readerConfig));
     }
 
     // Vibrator-like devices.
     if (classes.test(InputDeviceClass::VIBRATOR)) {
-        mappers.push_back(std::make_unique<VibratorInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(createInputMapper<VibratorInputMapper>(contextPtr, readerConfig));
     }
 
     // Battery-like devices or light-containing devices.
@@ -488,7 +488,7 @@
 
     // Cursor-like devices.
     if (classes.test(InputDeviceClass::CURSOR)) {
-        mappers.push_back(std::make_unique<CursorInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(createInputMapper<CursorInputMapper>(contextPtr, readerConfig));
     }
 
     // Touchscreens and touchpad devices.
@@ -501,21 +501,21 @@
             (identifier.product == 0x05c4 || identifier.product == 0x09cc);
     if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
         classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) {
-        mappers.push_back(std::make_unique<TouchpadInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(createInputMapper<TouchpadInputMapper>(contextPtr, readerConfig));
     } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
-        mappers.push_back(createInputMapper<MultiTouchInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(std::make_unique<MultiTouchInputMapper>(contextPtr, readerConfig));
     } else if (classes.test(InputDeviceClass::TOUCH)) {
-        mappers.push_back(createInputMapper<SingleTouchInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(std::make_unique<SingleTouchInputMapper>(contextPtr, readerConfig));
     }
 
     // Joystick-like devices.
     if (classes.test(InputDeviceClass::JOYSTICK)) {
-        mappers.push_back(std::make_unique<JoystickInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(createInputMapper<JoystickInputMapper>(contextPtr, readerConfig));
     }
 
     // Motion sensor enabled devices.
     if (classes.test(InputDeviceClass::SENSOR)) {
-        mappers.push_back(std::make_unique<SensorInputMapper>(contextPtr, readerConfig));
+        mappers.push_back(createInputMapper<SensorInputMapper>(contextPtr, readerConfig));
     }
 
     // External stylus-like devices.
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
index dab4661..061c6a3 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -199,6 +199,29 @@
         }
     }
 
+    // Send BUTTON_RELEASE events. (This has to happen before any UP events to avoid sending
+    // BUTTON_RELEASE events without any pointers.)
+    uint32_t newButtonState;
+    if (coords.size() - upSlots.size() + downSlots.size() == 0) {
+        // If there won't be any pointers down after this evdev sync, we won't be able to send
+        // button updates on their own, as motion events without pointers are invalid. To avoid
+        // erroneously reporting buttons being held for long periods, send BUTTON_RELEASE events for
+        // all pressed buttons when the last pointer is lifted.
+        //
+        // This also prevents us from sending BUTTON_PRESS events too early in the case of touchpads
+        // which report a button press one evdev sync before reporting a touch going down.
+        newButtonState = 0;
+    } else {
+        newButtonState = mCursorButtonAccumulator.getButtonState();
+    }
+    for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
+        if (!(newButtonState & button) && mButtonState & button) {
+            mButtonState &= ~button;
+            out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                                         coords, properties, /*actionButton=*/button));
+        }
+    }
+
     // For any touches that were lifted, send UP or POINTER_UP events.
     for (size_t slotNumber : upSlots) {
         const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber);
@@ -240,16 +263,11 @@
         out.push_back(makeMotionArgs(when, readTime, action, coords, properties));
     }
 
-    const uint32_t newButtonState = mCursorButtonAccumulator.getButtonState();
     for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
         if (newButtonState & button && !(mButtonState & button)) {
             mButtonState |= button;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords,
                                          properties, /*actionButton=*/button));
-        } else if (!(newButtonState & button) && mButtonState & button) {
-            mButtonState &= ~button;
-            out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                                         coords, properties, /*actionButton=*/button));
         }
     }
     return out;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 8ef5ff6..c684ed4 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -71,9 +71,7 @@
 CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext,
                                      const InputReaderConfiguration& readerConfig)
       : InputMapper(deviceContext, readerConfig),
-        mLastEventTime(std::numeric_limits<nsecs_t>::min()) {
-    configureWithZeroChanges(readerConfig);
-}
+        mLastEventTime(std::numeric_limits<nsecs_t>::min()) {}
 
 CursorInputMapper::~CursorInputMapper() {
     if (mPointerController != nullptr) {
@@ -142,46 +140,51 @@
                                                      ConfigurationChanges changes) {
     std::list<NotifyArgs> out = InputMapper::reconfigure(when, readerConfig, changes);
 
-    if (!changes.any()) {
-        configureWithZeroChanges(readerConfig);
-        return out;
+    if (!changes.any()) { // first time only
+        configureBasicParams();
     }
 
-    const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION &&
-            changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE);
+    const bool configurePointerCapture = !changes.any() ||
+            (mParameters.mode != Parameters::Mode::NAVIGATION &&
+             changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE));
     if (configurePointerCapture) {
         configureOnPointerCapture(readerConfig);
         out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
     }
 
-    if (changes.test(InputReaderConfiguration::Change::POINTER_SPEED) || configurePointerCapture) {
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) ||
+        configurePointerCapture) {
         configureOnChangePointerSpeed(readerConfig);
     }
 
-    if (changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) {
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) ||
+        configurePointerCapture) {
         configureOnChangeDisplayInfo(readerConfig);
     }
     return out;
 }
 
-void CursorInputMapper::configureParameters() {
-    mParameters.mode = Parameters::Mode::POINTER;
-    const PropertyMap& config = getDeviceContext().getConfiguration();
+CursorInputMapper::Parameters CursorInputMapper::computeParameters(
+        const InputDeviceContext& deviceContext) {
+    Parameters parameters;
+    parameters.mode = Parameters::Mode::POINTER;
+    const PropertyMap& config = deviceContext.getConfiguration();
     std::optional<std::string> cursorModeString = config.getString("cursor.mode");
     if (cursorModeString.has_value()) {
         if (*cursorModeString == "navigation") {
-            mParameters.mode = Parameters::Mode::NAVIGATION;
+            parameters.mode = Parameters::Mode::NAVIGATION;
         } else if (*cursorModeString != "pointer" && *cursorModeString != "default") {
             ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString->c_str());
         }
     }
 
-    mParameters.orientationAware = config.getBool("cursor.orientationAware").value_or(false);
+    parameters.orientationAware = config.getBool("cursor.orientationAware").value_or(false);
 
-    mParameters.hasAssociatedDisplay = false;
-    if (mParameters.mode == Parameters::Mode::POINTER || mParameters.orientationAware) {
-        mParameters.hasAssociatedDisplay = true;
+    parameters.hasAssociatedDisplay = false;
+    if (parameters.mode == Parameters::Mode::POINTER || parameters.orientationAware) {
+        parameters.hasAssociatedDisplay = true;
     }
+    return parameters;
 }
 
 void CursorInputMapper::dumpParameters(std::string& dump) {
@@ -424,22 +427,11 @@
     return mDisplayId;
 }
 
-void CursorInputMapper::configureWithZeroChanges(const InputReaderConfiguration& readerConfig) {
-    // Configuration with zero changes
-    configureBasicParams();
-    if (mParameters.mode != Parameters::Mode::NAVIGATION &&
-        readerConfig.pointerCaptureRequest.enable) {
-        configureOnPointerCapture(readerConfig);
-    }
-    configureOnChangePointerSpeed(readerConfig);
-    configureOnChangeDisplayInfo(readerConfig);
-}
-
 void CursorInputMapper::configureBasicParams() {
     mCursorScrollAccumulator.configure(getDeviceContext());
 
     // Configure basic parameters.
-    configureParameters();
+    mParameters = computeParameters(getDeviceContext());
 
     // Configure device mode.
     switch (mParameters.mode) {
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index caf2e5a..b879bfd 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -53,8 +53,10 @@
 
 class CursorInputMapper : public InputMapper {
 public:
-    explicit CursorInputMapper(InputDeviceContext& deviceContext,
-                               const InputReaderConfiguration& readerConfig);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~CursorInputMapper();
 
     virtual uint32_t getSources() const override;
@@ -125,15 +127,17 @@
     nsecs_t mDownTime;
     nsecs_t mLastEventTime;
 
-    void configureParameters();
+    explicit CursorInputMapper(InputDeviceContext& deviceContext,
+                               const InputReaderConfiguration& readerConfig);
     void dumpParameters(std::string& dump);
-    void configureWithZeroChanges(const InputReaderConfiguration& readerConfig);
     void configureBasicParams();
     void configureOnPointerCapture(const InputReaderConfiguration& config);
     void configureOnChangePointerSpeed(const InputReaderConfiguration& config);
     void configureOnChangeDisplayInfo(const InputReaderConfiguration& config);
 
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+
+    static Parameters computeParameters(const InputDeviceContext& deviceContext);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h
index 49673a2..313f092 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.h
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h
@@ -22,8 +22,10 @@
 
 class JoystickInputMapper : public InputMapper {
 public:
-    explicit JoystickInputMapper(InputDeviceContext& deviceContext,
-                                 const InputReaderConfiguration& readerConfig);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~JoystickInputMapper();
 
     virtual uint32_t getSources() const override;
@@ -87,6 +89,9 @@
         }
     };
 
+    explicit JoystickInputMapper(InputDeviceContext& deviceContext,
+                                 const InputReaderConfiguration& readerConfig);
+
     static Axis createAxis(const AxisInfo& AxisInfo, const RawAbsoluteAxisInfo& rawAxisInfo,
                            bool explicitlyMapped);
 
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index 1d788df..f300ee1 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -27,6 +27,8 @@
     friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
                                                 const InputReaderConfiguration& readerConfig,
                                                 Args... args);
+    explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
+                                   const InputReaderConfiguration& readerConfig);
 
     ~MultiTouchInputMapper() override;
 
@@ -39,8 +41,6 @@
     bool hasStylus() const override;
 
 private:
-    explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
-                                   const InputReaderConfiguration& readerConfig);
     // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
     // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
     // It is used to simulate stylus events for debugging and testing on a device that does not
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index d3dcbe1..9e2e8c4 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -25,8 +25,10 @@
 
 class RotaryEncoderInputMapper : public InputMapper {
 public:
-    explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
-                                      const InputReaderConfiguration& readerConfig);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~RotaryEncoderInputMapper();
 
     virtual uint32_t getSources() const override;
@@ -45,6 +47,8 @@
     float mScalingFactor;
     ui::Rotation mOrientation;
 
+    explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
+                                      const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
 };
 
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h
index 1f82559..a55dcd1 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.h
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.h
@@ -27,8 +27,10 @@
 
 class SensorInputMapper : public InputMapper {
 public:
-    explicit SensorInputMapper(InputDeviceContext& deviceContext,
-                               const InputReaderConfiguration& readerConfig);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     ~SensorInputMapper() override;
 
     uint32_t getSources() const override;
@@ -106,6 +108,9 @@
         }
     };
 
+    explicit SensorInputMapper(InputDeviceContext& deviceContext,
+                               const InputReaderConfiguration& readerConfig);
+
     static Axis createAxis(const AxisInfo& AxisInfo, const RawAbsoluteAxisInfo& rawAxisInfo);
 
     // Axes indexed by raw ABS_* axis index.
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
index 7726bfb..dac53cf 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
@@ -27,6 +27,8 @@
     friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
                                                 const InputReaderConfiguration& readerConfig,
                                                 Args... args);
+    explicit SingleTouchInputMapper(InputDeviceContext& deviceContext,
+                                    const InputReaderConfiguration& readerConfig);
 
     ~SingleTouchInputMapper() override;
 
@@ -40,8 +42,6 @@
 
 private:
     SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
-    explicit SingleTouchInputMapper(InputDeviceContext& deviceContext,
-                                    const InputReaderConfiguration& readerConfig);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h
index 7ec282b..2fb48bb 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h
@@ -22,8 +22,10 @@
 
 class SwitchInputMapper : public InputMapper {
 public:
-    explicit SwitchInputMapper(InputDeviceContext& deviceContext,
-                               const InputReaderConfiguration& readerConfig);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~SwitchInputMapper();
 
     virtual uint32_t getSources() const override;
@@ -36,6 +38,8 @@
     uint32_t mSwitchValues;
     uint32_t mUpdatedSwitchMask;
 
+    explicit SwitchInputMapper(InputDeviceContext& deviceContext,
+                               const InputReaderConfiguration& readerConfig);
     void processSwitch(int32_t switchCode, int32_t switchValue);
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when);
 };
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 3128d18..23d0fd3 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -40,8 +40,10 @@
 
 class TouchpadInputMapper : public InputMapper {
 public:
-    explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
-                                 const InputReaderConfiguration& readerConfig);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     ~TouchpadInputMapper();
 
     uint32_t getSources() const override;
@@ -58,6 +60,8 @@
 
 private:
     void resetGestureInterpreter(nsecs_t when);
+    explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
+                                 const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> sendHardwareState(nsecs_t when, nsecs_t readTime,
                                                           SelfContainedHardwareState schs);
     [[nodiscard]] std::list<NotifyArgs> processGestures(nsecs_t when, nsecs_t readTime);
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h
index 384c075..9079c73 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.h
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h
@@ -22,8 +22,10 @@
 
 class VibratorInputMapper : public InputMapper {
 public:
-    explicit VibratorInputMapper(InputDeviceContext& deviceContext,
-                                 const InputReaderConfiguration& readerConfig);
+    template <class T, class... Args>
+    friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+                                                const InputReaderConfiguration& readerConfig,
+                                                Args... args);
     virtual ~VibratorInputMapper();
 
     virtual uint32_t getSources() const override;
@@ -46,6 +48,8 @@
     ssize_t mIndex;
     nsecs_t mNextStepTime;
 
+    explicit VibratorInputMapper(InputDeviceContext& deviceContext,
+                                 const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> nextStep();
     [[nodiscard]] NotifyVibratorStateArgs stopVibrating();
 };
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index fd2be5f..7eca6fa 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -132,7 +132,7 @@
         case kGestureTypeScroll:
             return handleScroll(when, readTime, gesture);
         case kGestureTypeFling:
-            return {handleFling(when, readTime, gesture)};
+            return handleFling(when, readTime, gesture);
         case kGestureTypeSwipe:
             return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx,
                                           gesture.details.swipe.dy);
@@ -149,7 +149,8 @@
     }
 }
 
-NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
+NotifyMotionArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime,
+                                              const Gesture& gesture) {
     float deltaX = gesture.details.move.dx;
     float deltaY = gesture.details.move.dy;
     rotateDelta(mOrientation, &deltaX, &deltaY);
@@ -312,19 +313,39 @@
     return out;
 }
 
-NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
-    // We don't actually want to use the gestures library's fling velocity values (to ensure
-    // consistency between touchscreen and touchpad flings), so we're just using the "start fling"
-    // gestures as a marker for the end of a two-finger scroll gesture.
-    if (gesture.details.fling.fling_state != GESTURES_FLING_START ||
-        mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
-        return {};
+std::list<NotifyArgs> GestureConverter::handleFling(nsecs_t when, nsecs_t readTime,
+                                                    const Gesture& gesture) {
+    switch (gesture.details.fling.fling_state) {
+        case GESTURES_FLING_START:
+            if (mCurrentClassification == MotionClassification::TWO_FINGER_SWIPE) {
+                // We don't actually want to use the gestures library's fling velocity values (to
+                // ensure consistency between touchscreen and touchpad flings), so we're just using
+                // the "start fling" gestures as a marker for the end of a two-finger scroll
+                // gesture.
+                return {endScroll(when, readTime)};
+            }
+            break;
+        case GESTURES_FLING_TAP_DOWN:
+            if (mCurrentClassification == MotionClassification::NONE) {
+                // Use the tap down state of a fling gesture as an indicator that a contact
+                // has been initiated with the touchpad. We treat this as a move event with zero
+                // magnitude, which will also result in the pointer icon being updated.
+                // TODO(b/282023644): Add a signal in libgestures for when a stable contact has been
+                //  initiated with a touchpad.
+                return {handleMove(when, readTime,
+                                   Gesture(kGestureMove, gesture.start_time, gesture.end_time,
+                                           /*dx=*/0.f,
+                                           /*dy=*/0.f))};
+            }
+            break;
+        default:
+            break;
     }
 
-    return endScroll(when, readTime);
+    return {};
 }
 
-NotifyArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
+NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
     const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0);
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0);
@@ -507,14 +528,29 @@
                                                   const PointerProperties* pointerProperties,
                                                   const PointerCoords* pointerCoords,
                                                   float xCursorPosition, float yCursorPosition) {
-    return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
-                            mPointerController->getDisplayId(), /* policyFlags= */ POLICY_FLAG_WAKE,
-                            action, /* actionButton= */ actionButton, /* flags= */ 0,
-                            mReaderContext.getGlobalMetaState(), buttonState,
-                            mCurrentClassification, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
-                            pointerProperties, pointerCoords, /* xPrecision= */ 1.0f,
-                            /* yPrecision= */ 1.0f, xCursorPosition, yCursorPosition,
-                            /* downTime= */ mDownTime, /* videoFrames= */ {});
+    return {mReaderContext.getNextId(),
+            when,
+            readTime,
+            mDeviceId,
+            SOURCE,
+            mPointerController->getDisplayId(),
+            /* policyFlags= */ POLICY_FLAG_WAKE,
+            action,
+            /* actionButton= */ actionButton,
+            /* flags= */ 0,
+            mReaderContext.getGlobalMetaState(),
+            buttonState,
+            mCurrentClassification,
+            AMOTION_EVENT_EDGE_FLAG_NONE,
+            pointerCount,
+            pointerProperties,
+            pointerCoords,
+            /* xPrecision= */ 1.0f,
+            /* yPrecision= */ 1.0f,
+            xCursorPosition,
+            yCursorPosition,
+            /* downTime= */ mDownTime,
+            /* videoFrames= */ {}};
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 70e8fb7..b613b88 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -52,14 +52,16 @@
                                                       const Gesture& gesture);
 
 private:
-    [[nodiscard]] NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
+    [[nodiscard]] NotifyMotionArgs handleMove(nsecs_t when, nsecs_t readTime,
+                                              const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
                                                             const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> releaseAllButtons(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime,
                                                      const Gesture& gesture);
-    [[nodiscard]] NotifyArgs handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
-    [[nodiscard]] NotifyArgs endScroll(nsecs_t when, nsecs_t readTime);
+    [[nodiscard]] std::list<NotifyArgs> handleFling(nsecs_t when, nsecs_t readTime,
+                                                    const Gesture& gesture);
+    [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime);
 
     [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime,
                                                                uint32_t fingerCount, float dx,
diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
index 3dc5152..99a6a1f 100644
--- a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
+++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
@@ -781,4 +781,175 @@
                       WithPointerId(/*index=*/1, /*id=*/0)));
 }
 
+// Motion events without any pointers are invalid, so when a button press is reported in the same
+// frame as a touch down, the button press must be reported second. Similarly with a button release
+// and a touch lift.
+TEST_F(CapturedTouchpadEventConverterTest,
+       ButtonPressedAndReleasedInSameFrameAsTouch_ReportedWithPointers) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 1);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u),
+                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 0);
+    args = processSync(conv);
+    ASSERT_EQ(3u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u),
+                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(0)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_UP));
+}
+
+// Some touchpads sometimes report a button press before they report the finger touching the pad. In
+// that case we need to wait until the touch comes to report the button press.
+TEST_F(CapturedTouchpadEventConverterTest, ButtonPressedBeforeTouch_ReportedOnceTouchOccurs) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 1);
+    ASSERT_EQ(0u, processSync(conv).size());
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u),
+                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+}
+
+// When all fingers are lifted from a touchpad, we should release any buttons that are down, since
+// we won't be able to report them being lifted later if no pointers are present.
+TEST_F(CapturedTouchpadEventConverterTest, ButtonReleasedAfterTouchLifts_ReportedWithLift) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 1);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+    args = processSync(conv);
+    ASSERT_EQ(3u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u),
+                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(0)));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 0);
+    ASSERT_EQ(0u, processSync(conv).size());
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, MultipleButtonsPressedDuringTouch_ReportedCorrectly) {
+    CapturedTouchpadEventConverter conv = createConverter();
+
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 1);
+    std::list<NotifyArgs> args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+
+    processAxis(conv, EV_KEY, BTN_RIGHT, 1);
+    args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                      AMOTION_EVENT_BUTTON_SECONDARY)));
+
+    processAxis(conv, EV_KEY, BTN_LEFT, 0);
+    args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY)));
+
+    processAxis(conv, EV_KEY, BTN_RIGHT, 0);
+    args = processSync(conv);
+    ASSERT_EQ(2u, args.size());
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+    args.pop_front();
+    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0)));
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index c6d541e..a723636 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -888,4 +888,21 @@
                       WithToolType(ToolType::FINGER)));
 }
 
+TEST_F(GestureConverterTest, FlingTapDown) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                           /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
+                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X, POINTER_Y));
+    ASSERT_TRUE(mFakePointerController->isPointerShown());
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index f6f02d8..3f2658a 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -86,6 +86,8 @@
         AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 static constexpr int32_t POINTER_1_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
 // The default pid and uid for windows created on the primary display by the test.
 static constexpr int32_t WINDOW_PID = 999;
@@ -1562,8 +1564,7 @@
 
         // Set mouse cursor position for the most common cases to avoid boilerplate.
         if (mSource == AINPUT_SOURCE_MOUSE &&
-            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
-            mPointers.size() == 1) {
+            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) {
             mRawXCursorPosition = pointerCoords[0].getX();
             mRawYCursorPosition = pointerCoords[0].getY();
         }
@@ -1660,6 +1661,11 @@
         return *this;
     }
 
+    MotionArgsBuilder& classification(MotionClassification classification) {
+        mClassification = classification;
+        return *this;
+    }
+
     NotifyMotionArgs build() {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
@@ -1670,15 +1676,14 @@
 
         // Set mouse cursor position for the most common cases to avoid boilerplate.
         if (mSource == AINPUT_SOURCE_MOUSE &&
-            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
-            mPointers.size() == 1) {
+            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) {
             mRawXCursorPosition = pointerCoords[0].getX();
             mRawYCursorPosition = pointerCoords[0].getY();
         }
 
         NotifyMotionArgs args(InputEvent::nextId(), mEventTime, /*readTime=*/mEventTime, mDeviceId,
                               mSource, mDisplayId, mPolicyFlags, mAction, mActionButton, mFlags,
-                              AMETA_NONE, mButtonState, MotionClassification::NONE, /*edgeFlags=*/0,
+                              AMETA_NONE, mButtonState, mClassification, /*edgeFlags=*/0,
                               mPointers.size(), pointerProperties.data(), pointerCoords.data(),
                               /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition,
                               mRawYCursorPosition, mDownTime, /*videoFrames=*/{});
@@ -1697,6 +1702,7 @@
     int32_t mActionButton{0};
     int32_t mButtonState{0};
     int32_t mFlags{0};
+    MotionClassification mClassification{MotionClassification::NONE};
     float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
 
@@ -4003,6 +4009,126 @@
     EXPECT_EQ(-10, event->getY(1)); // -50 + 40
 }
 
+TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+    sp<FakeWindowHandle> trustedOverlay =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Trusted Overlay",
+                                       ADISPLAY_ID_DEFAULT);
+    trustedOverlay->setSpy(true);
+    trustedOverlay->setTrustedOverlay(true);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {trustedOverlay, window}}});
+
+    // Start a three-finger touchpad swipe
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+    trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_2_DOWN));
+
+    // Move the swipe a bit
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+    // End the swipe
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_2_UP));
+    trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_1_UP));
+    trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_UP));
+
+    window->assertNoEvents();
+}
+
+TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeNotSentToSingleWindow) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Start a three-finger touchpad swipe
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(100))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    // Move the swipe a bit
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    // End the swipe
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105))
+                                      .classification(MotionClassification::MULTI_FINGER_SWIPE)
+                                      .build());
+
+    window->assertNoEvents();
+}
+
 /**
  * Ensure the correct coordinate spaces are used by InputDispatcher.
  *
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index ae3ae6c..9fbe762 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2603,13 +2603,13 @@
 };
 
 TEST_F(SwitchInputMapperTest, GetSources) {
-    SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
+    SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
 
     ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper.getSources());
 }
 
 TEST_F(SwitchInputMapperTest, GetSwitchState) {
-    SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
+    SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
 
     mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 1);
     ASSERT_EQ(1, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
@@ -2619,7 +2619,7 @@
 }
 
 TEST_F(SwitchInputMapperTest, Process) {
-    SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
+    SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
     std::list<NotifyArgs> out;
     out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_LID, 1);
     ASSERT_TRUE(out.empty());
@@ -2645,13 +2645,13 @@
 };
 
 TEST_F(VibratorInputMapperTest, GetSources) {
-    VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+    VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources());
 }
 
 TEST_F(VibratorInputMapperTest, GetVibratorIds) {
-    VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+    VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
 
     ASSERT_EQ(mapper.getVibratorIds().size(), 2U);
 }
@@ -2659,7 +2659,7 @@
 TEST_F(VibratorInputMapperTest, Vibrate) {
     constexpr uint8_t DEFAULT_AMPLITUDE = 192;
     constexpr int32_t VIBRATION_TOKEN = 100;
-    VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+    VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
 
     VibrationElement pattern(2);
     VibrationSequence sequence(2);
@@ -2784,7 +2784,7 @@
 }
 
 TEST_F(SensorInputMapperTest, GetSources) {
-    SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
 
     ASSERT_EQ(static_cast<uint32_t>(AINPUT_SOURCE_SENSOR), mapper.getSources());
 }
@@ -2792,7 +2792,7 @@
 TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) {
     setAccelProperties();
     prepareAccelAxes();
-    SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
 
     ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::ACCELEROMETER,
                                     std::chrono::microseconds(10000),
@@ -2822,7 +2822,7 @@
 TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) {
     setGyroProperties();
     prepareGyroAxes();
-    SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
 
     ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::GYROSCOPE,
                                     std::chrono::microseconds(10000),
@@ -3862,21 +3862,21 @@
 
 TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
 }
 
 TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper.getSources());
 }
 
 TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     InputDeviceInfo info;
     mapper.populateDeviceInfo(info);
@@ -3906,7 +3906,7 @@
 
 TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     InputDeviceInfo info;
     mapper.populateDeviceInfo(info);
@@ -3924,7 +3924,7 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
 
@@ -4012,7 +4012,7 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyMotionArgs args;
 
@@ -4036,7 +4036,7 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyMotionArgs args;
 
@@ -4065,7 +4065,7 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
     addConfigurationProperty("cursor.mode", "navigation");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyMotionArgs args;
 
@@ -4114,7 +4114,7 @@
     // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not
     // need to be rotated.
     addConfigurationProperty("cursor.orientationAware", "1");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     prepareDisplay(ui::ROTATION_90);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
@@ -4132,7 +4132,7 @@
     addConfigurationProperty("cursor.mode", "navigation");
     // Since InputReader works in the un-rotated coordinate space, only devices that are not
     // orientation-aware are affected by display rotation.
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     clearViewports();
     prepareDisplay(ui::ROTATION_0);
@@ -4181,7 +4181,7 @@
 
 TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
@@ -4435,7 +4435,7 @@
 
 TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
     mFakePointerController->setPosition(100, 200);
@@ -4456,7 +4456,7 @@
 TEST_F(CursorInputMapperTest, Process_PointerCapture) {
     addConfigurationProperty("cursor.mode", "pointer");
     mFakePolicy->setPointerCapture(true);
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyDeviceResetArgs resetArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -4548,7 +4548,7 @@
     const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f,
                                                /*high threshold=*/100.f, /*acceleration=*/10.f);
     mFakePolicy->setVelocityControlParams(testParams);
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyDeviceResetArgs resetArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -4589,7 +4589,7 @@
 
 TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     NotifyDeviceResetArgs resetArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -4630,7 +4630,7 @@
 }
 
 TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) {
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     // Set up the default display.
     prepareDisplay(ui::ROTATION_90);
@@ -4656,7 +4656,7 @@
 }
 
 TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) {
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     // Set up the default display.
     prepareDisplay(ui::ROTATION_90);
@@ -4682,7 +4682,7 @@
 }
 
 TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) {
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     // Set up the default display as the display on which the pointer should be shown.
     prepareDisplay(ui::ROTATION_90);
@@ -4715,7 +4715,7 @@
 
 TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     nsecs_t kernelEventTime = ARBITRARY_TIME;
     nsecs_t expectedEventTime = ARBITRARY_TIME;
@@ -4742,7 +4742,7 @@
 
 TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     nsecs_t expectedEventTime = ARBITRARY_TIME;
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
@@ -4779,7 +4779,7 @@
 
 TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) {
     addConfigurationProperty("cursor.mode", "pointer");
-    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+    CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
 
     nsecs_t kernelEventTime = ARBITRARY_TIME;
     nsecs_t expectedEventTime = ARBITRARY_TIME;
@@ -10904,7 +10904,7 @@
 
 TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) {
     prepareAxes();
-    JoystickInputMapper& mapper = addMapperAndConfigure<JoystickInputMapper>();
+    JoystickInputMapper& mapper = constructAndAddMapper<JoystickInputMapper>();
 
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
 
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 20f4de1..f6ca9e2 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -50,8 +50,6 @@
 
 namespace hal = hardware::graphics::composer::hal;
 
-ui::Transform::RotationFlags DisplayDevice::sPrimaryDisplayRotationFlags = ui::Transform::ROT_0;
-
 DisplayDeviceCreationArgs::DisplayDeviceCreationArgs(
         const sp<SurfaceFlinger>& flinger, HWComposer& hwComposer, const wp<IBinder>& displayToken,
         std::shared_ptr<compositionengine::Display> compositionDisplay)
@@ -282,10 +280,6 @@
                                   Rect orientedDisplaySpaceRect) {
     mOrientation = orientation;
 
-    if (isPrimary()) {
-        sPrimaryDisplayRotationFlags = ui::Transform::toRotationFlags(orientation);
-    }
-
     // We need to take care of display rotation for globalTransform for case if the panel is not
     // installed aligned with device orientation.
     const auto transformOrientation = orientation + mPhysicalOrientation;
@@ -326,10 +320,6 @@
     return mStagedBrightness;
 }
 
-ui::Transform::RotationFlags DisplayDevice::getPrimaryDisplayRotationFlags() {
-    return sPrimaryDisplayRotationFlags;
-}
-
 void DisplayDevice::dump(utils::Dumper& dumper) const {
     using namespace std::string_view_literals;
 
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 51876e7..dc5f8a8 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -109,8 +109,6 @@
     ui::Rotation getPhysicalOrientation() const { return mPhysicalOrientation; }
     ui::Rotation getOrientation() const { return mOrientation; }
 
-    static ui::Transform::RotationFlags getPrimaryDisplayRotationFlags();
-
     std::optional<float> getStagedBrightness() const REQUIRES(kMainThreadContext);
     ui::Transform::RotationFlags getTransformHint() const;
     const ui::Transform& getTransform() const;
@@ -274,8 +272,6 @@
     const ui::Rotation mPhysicalOrientation;
     ui::Rotation mOrientation = ui::ROTATION_0;
 
-    static ui::Transform::RotationFlags sPrimaryDisplayRotationFlags;
-
     // Allow nullopt as initial power mode.
     using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>;
     std::optional<TracedPowerMode> mPowerMode;
diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
index 76b36fe..218a64a 100644
--- a/services/surfaceflinger/FrontEnd/DisplayInfo.h
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -28,7 +28,7 @@
     ui::Transform transform;
     bool receivesInput;
     bool isSecure;
-    // TODO(b/238781169) can eliminate once sPrimaryDisplayRotationFlags is removed.
+    // TODO(b/259407931): can eliminate once SurfaceFlinger::sActiveDisplayRotationFlags is removed.
     bool isPrimary;
     bool isVirtual;
     ui::Transform::RotationFlags rotationFlags;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index ce7d37e..985c6f9 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -667,6 +667,7 @@
     snapshot.relativeLayerMetadata.mMap.clear();
 }
 
+// TODO (b/259407931): Remove.
 uint32_t getPrimaryDisplayRotationFlags(
         const display::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays) {
     for (auto& [_, display] : displays) {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 3371ae2..f12aab7 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -254,7 +254,8 @@
         mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount();
     }
     if (mHadClonedChild) {
-        mFlinger->mNumClones--;
+        auto& roots = mFlinger->mLayerMirrorRoots;
+        roots.erase(std::remove(roots.begin(), roots.end(), this), roots.end());
     }
     if (hasTrustedPresentationListener()) {
         mFlinger->mNumTrustedPresentationListeners--;
@@ -1649,8 +1650,8 @@
 
 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);
+    StringAppendF(&result, "Layer %s%s pid:%d uid:%d%s\n", getName().c_str(), hasBuffer.c_str(),
+                  mOwnerPid, mOwnerUid, isHandleAlive() ? " handleAlive" : "");
 }
 
 void Layer::onDisconnect() {
@@ -2594,7 +2595,7 @@
     mDrawingState.inputInfo = tmpInputInfo;
 }
 
-void Layer::updateMirrorInfo() {
+bool Layer::updateMirrorInfo(const std::deque<Layer*>& cloneRootsPendingUpdates) {
     if (mClonedChild == nullptr || !mClonedChild->isClonedFromAlive()) {
         // If mClonedChild is null, there is nothing to mirror. If isClonedFromAlive returns false,
         // it means that there is a clone, but the layer it was cloned from has been destroyed. In
@@ -2602,7 +2603,7 @@
         // destroyed. The root, this layer, will still be around since the client can continue
         // to hold a reference, but no cloned layers will be displayed.
         mClonedChild = nullptr;
-        return;
+        return true;
     }
 
     std::map<sp<Layer>, sp<Layer>> clonedLayersMap;
@@ -2617,6 +2618,13 @@
     mClonedChild->updateClonedDrawingState(clonedLayersMap);
     mClonedChild->updateClonedChildren(sp<Layer>::fromExisting(this), clonedLayersMap);
     mClonedChild->updateClonedRelatives(clonedLayersMap);
+
+    for (Layer* root : cloneRootsPendingUpdates) {
+        if (clonedLayersMap.find(sp<Layer>::fromExisting(root)) != clonedLayersMap.end()) {
+            return false;
+        }
+    }
+    return true;
 }
 
 void Layer::updateClonedDrawingState(std::map<sp<Layer>, sp<Layer>>& clonedLayersMap) {
@@ -2764,7 +2772,7 @@
 void Layer::setClonedChild(const sp<Layer>& clonedChild) {
     mClonedChild = clonedChild;
     mHadClonedChild = true;
-    mFlinger->mNumClones++;
+    mFlinger->mLayerMirrorRoots.push_back(this);
 }
 
 bool Layer::setDropInputMode(gui::DropInputMode mode) {
@@ -2977,7 +2985,7 @@
     if (mDrawingState.bufferTransform & ui::Transform::ROT_90) {
         std::swap(bufferWidth, bufferHeight);
     }
-    uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+    uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags();
     if (mDrawingState.transformToDisplayInverse) {
         if (invTransform & ui::Transform::ROT_90) {
             std::swap(bufferWidth, bufferHeight);
@@ -3077,6 +3085,7 @@
     mDrawingState.desiredPresentTime = desiredPresentTime;
     mDrawingState.isAutoTimestamp = isAutoTimestamp;
     mDrawingState.latchedVsyncId = info.vsyncId;
+    mDrawingState.useVsyncIdForRefreshRateSelection = info.useForRefreshRateSelection;
     mDrawingState.modified = true;
     if (!buffer) {
         resetDrawingStateBufferInfo();
@@ -3139,15 +3148,31 @@
 }
 
 void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps) {
+    ATRACE_CALL();
     const nsecs_t presentTime = [&] {
-        if (!mDrawingState.isAutoTimestamp) return mDrawingState.desiredPresentTime;
+        if (!mDrawingState.isAutoTimestamp) {
+            ATRACE_FORMAT_INSTANT("desiredPresentTime");
+            return mDrawingState.desiredPresentTime;
+        }
 
-        const auto prediction = mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(
-                mDrawingState.latchedVsyncId);
-        if (prediction.has_value()) return prediction->presentTime;
+        if (mDrawingState.useVsyncIdForRefreshRateSelection) {
+            const auto prediction =
+                    mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(
+                            mDrawingState.latchedVsyncId);
+            if (prediction.has_value()) {
+                ATRACE_FORMAT_INSTANT("predictedPresentTime");
+                return prediction->presentTime;
+            }
+        }
 
         return static_cast<nsecs_t>(0);
     }();
+
+    if (ATRACE_ENABLED() && presentTime > 0) {
+        const auto presentIn = TimePoint::fromNs(presentTime) - TimePoint::now();
+        ATRACE_FORMAT_INSTANT("presentIn %s", to_string(presentIn).c_str());
+    }
+
     mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime,
                                              scheduler::LayerHistory::LayerUpdateType::Buffer);
 }
@@ -3287,7 +3312,7 @@
     }
 
     if (getTransformToDisplayInverse()) {
-        uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+        uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags();
         if (invTransform & ui::Transform::ROT_90) {
             std::swap(bufWidth, bufHeight);
         }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 2640c92..38590e6 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -234,6 +234,7 @@
         float desiredHdrSdrRatio = 1.f;
         gui::CachingHint cachingHint = gui::CachingHint::Enabled;
         int64_t latchedVsyncId = 0;
+        bool useVsyncIdForRefreshRateSelection = false;
     };
 
     explicit Layer(const LayerCreationArgs& args);
@@ -651,7 +652,7 @@
 
     gui::WindowInfo::Type getWindowType() const { return mWindowType; }
 
-    void updateMirrorInfo();
+    bool updateMirrorInfo(const std::deque<Layer*>& cloneRootsPendingUpdates);
 
     /*
      * doTransaction - process the transaction. This is a good place to figure
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index e713263..f855f27 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -25,8 +25,8 @@
 #include <system/window.h>
 #include <utils/Log.h>
 
-#include "DisplayDevice.h"
 #include "LayerFE.h"
+#include "SurfaceFlinger.h"
 
 namespace android {
 
@@ -260,7 +260,7 @@
          * the code below applies the primary display's inverse transform to
          * the texture transform
          */
-        uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags();
+        uint32_t transform = SurfaceFlinger::getActiveDisplayRotationFlags();
         mat4 tr = inverseOrientation(transform);
 
         /**
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index f6b5afc..d606cff 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -85,7 +85,7 @@
     // If layer is offscreen, update mirroring info if it exists
     if (mLayer->isRemovedFromCurrentState()) {
         mLayer->traverse(LayerVector::StateSet::Drawing,
-                         [&](Layer* layer) { layer->updateMirrorInfo(); });
+                         [&](Layer* layer) { layer->updateMirrorInfo({}); });
         mLayer->traverse(LayerVector::StateSet::Drawing,
                          [&](Layer* layer) { layer->updateCloneBufferInfo(); });
     }
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index af9acf3..281b0ae 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -612,6 +612,15 @@
                 preferredExpectedPresentationTime + multiplier * frameInterval;
         if (expectedPresentationTime >= preferredExpectedPresentationTime +
                     scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count()) {
+            if (currentIndex == 0) {
+                ALOGW("%s: Expected present time is too far in the future but no timelines are "
+                      "valid. preferred EPT=%" PRId64 ", Calculated EPT=%" PRId64
+                      ", multiplier=%" PRId64 ", frameInterval=%" PRId64 ", threshold=%" PRId64,
+                      __func__, preferredExpectedPresentationTime, expectedPresentationTime,
+                      multiplier, frameInterval,
+                      static_cast<int64_t>(
+                              scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count()));
+            }
             break;
         }
 
@@ -625,6 +634,20 @@
                  .expectedPresentationTime = expectedPresentationTime};
         currentIndex++;
     }
+
+    if (currentIndex == 0) {
+        ALOGW("%s: No timelines are valid. preferred EPT=%" PRId64 ", frameInterval=%" PRId64
+              ", threshold=%" PRId64,
+              __func__, preferredExpectedPresentationTime, frameInterval,
+              static_cast<int64_t>(scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count()));
+        outVsyncEventData.frameTimelines[currentIndex] =
+                {.vsyncId = generateToken(timestamp, preferredDeadlineTimestamp,
+                                          preferredExpectedPresentationTime),
+                 .deadlineTimestamp = preferredDeadlineTimestamp,
+                 .expectedPresentationTime = preferredExpectedPresentationTime};
+        currentIndex++;
+    }
+
     outVsyncEventData.frameTimelinesLength = currentIndex;
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index dfe8c72..5d96fc4 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -356,6 +356,8 @@
             PermissionCache::checkPermission(permission, pid, uid);
 }
 
+ui::Transform::RotationFlags SurfaceFlinger::sActiveDisplayRotationFlags = ui::Transform::ROT_0;
+
 SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag)
       : mFactory(factory),
         mPid(getpid()),
@@ -2619,7 +2621,7 @@
 
     refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
     refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
-    refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags();
+    refreshArgs.internalDisplayRotationFlags = getActiveDisplayRotationFlags();
 
     if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) {
         refreshArgs.colorTransformMatrix = mDrawingState.colorMatrix;
@@ -3563,6 +3565,8 @@
                                    currentState.orientedDisplaySpaceRect);
             if (display->getId() == mActiveDisplayId) {
                 mActiveDisplayTransformHint = display->getTransformHint();
+                sActiveDisplayRotationFlags =
+                        ui::Transform::toRotationFlags(display->getOrientation());
             }
         }
         if (currentState.width != drawingState.width ||
@@ -4025,8 +4029,24 @@
     }
 
     commitOffscreenLayers();
-    if (mNumClones > 0) {
-        mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); });
+    if (mLayerMirrorRoots.size() > 0) {
+        std::deque<Layer*> pendingUpdates;
+        pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(),
+                              mLayerMirrorRoots.end());
+        std::vector<Layer*> needsUpdating;
+        for (Layer* cloneRoot : mLayerMirrorRoots) {
+            pendingUpdates.pop_front();
+            if (cloneRoot->isRemovedFromCurrentState()) {
+                continue;
+            }
+            if (cloneRoot->updateMirrorInfo(pendingUpdates)) {
+            } else {
+                needsUpdating.push_back(cloneRoot);
+            }
+        }
+        for (Layer* cloneRoot : needsUpdating) {
+            cloneRoot->updateMirrorInfo({});
+        }
     }
 }
 
@@ -4133,7 +4153,7 @@
         mBootStage = BootStage::BOOTANIMATION;
     }
 
-    if (mNumClones > 0) {
+    if (mLayerMirrorRoots.size() > 0) {
         mDrawingState.traverse([&](Layer* layer) { layer->updateCloneBufferInfo(); });
     }
 
@@ -4158,8 +4178,8 @@
                     leakingParentLayerFound = true;
                     sp<Layer> parent = sp<Layer>::fromExisting(layer);
                     while (parent) {
-                        ALOGE("Parent Layer: %s handleIsAlive: %s", parent->getName().c_str(),
-                              std::to_string(parent->isHandleAlive()).c_str());
+                        ALOGE("Parent Layer: %s%s", parent->getName().c_str(),
+                              (parent->isHandleAlive() ? "handleAlive" : ""));
                         parent = parent->getParent();
                     }
                     // Sample up to 100 layers
@@ -4174,21 +4194,28 @@
                 }
             });
 
-            ALOGE("Dumping random sampling of on-screen layers: ");
+            int numLayers = 0;
+            mDrawingState.traverse([&](Layer* layer) { numLayers++; });
+
+            ALOGE("Dumping random sampling of on-screen layers total(%u):", numLayers);
             mDrawingState.traverse([&](Layer* layer) {
                 // Aim to dump about 200 layers to avoid totally trashing
                 // logcat. On the other hand, if there really are 4096 layers
                 // something has gone totally wrong its probably the most
                 // useful information in logcat.
                 if (rand() % 20 == 13) {
-                    ALOGE("Layer: %s", layer->getName().c_str());
+                    ALOGE("Layer: %s%s", layer->getName().c_str(),
+                          (layer->isHandleAlive() ? "handleAlive" : ""));
+                    std::this_thread::sleep_for(std::chrono::milliseconds(5));
                 }
             });
             ALOGE("Dumping random sampling of off-screen layers total(%zu): ",
                   mOffscreenLayers.size());
             for (Layer* offscreenLayer : mOffscreenLayers) {
                 if (rand() % 20 == 13) {
-                    ALOGE("Offscreen-layer: %s", offscreenLayer->getName().c_str());
+                    ALOGE("Offscreen-layer: %s%s", offscreenLayer->getName().c_str(),
+                          (offscreenLayer->isHandleAlive() ? "handleAlive" : ""));
+                    std::this_thread::sleep_for(std::chrono::milliseconds(5));
                 }
             }
         }));
@@ -4474,7 +4501,8 @@
         const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
         InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp,
         const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
-        const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
+        const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
+        const std::vector<uint64_t>& mergedTransactionIds) {
     ATRACE_CALL();
 
     IPCThreadState* ipc = IPCThreadState::self();
@@ -4563,7 +4591,8 @@
                            listenerCallbacks,
                            originPid,
                            originUid,
-                           transactionId};
+                           transactionId,
+                           mergedTransactionIds};
 
     if (mTransactionTracing) {
         mTransactionTracing->addQueuedTransaction(state);
@@ -5474,14 +5503,19 @@
             onActiveDisplayChangedLocked(activeDisplay.get(), *display);
         }
 
-        // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315.
-        // We can merge the syscall later.
-        if (SurfaceFlinger::setSchedAttr(true) != NO_ERROR) {
-            ALOGW("Couldn't set uclamp.min on display on: %s\n", strerror(errno));
+        if (displayId == mActiveDisplayId) {
+            // TODO(b/281692563): Merge the syscalls. For now, keep uclamp in a separate syscall and
+            // set it before SCHED_FIFO due to b/190237315.
+            if (setSchedAttr(true) != NO_ERROR) {
+                ALOGW("Failed to set uclamp.min after powering on active display: %s",
+                      strerror(errno));
+            }
+            if (setSchedFifo(true) != NO_ERROR) {
+                ALOGW("Failed to set SCHED_FIFO after powering on active display: %s",
+                      strerror(errno));
+            }
         }
-        if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) {
-            ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno));
-        }
+
         getHwComposer().setPowerMode(displayId, mode);
         if (displayId == mActiveDisplayId && mode != hal::PowerMode::DOZE_SUSPEND) {
             setHWCVsyncEnabled(displayId,
@@ -5495,15 +5529,21 @@
         scheduleComposite(FrameHint::kActive);
     } else if (mode == hal::PowerMode::OFF) {
         // Turn off the display
-        if (SurfaceFlinger::setSchedFifo(false) != NO_ERROR) {
-            ALOGW("Couldn't set SCHED_OTHER on display off: %s\n", strerror(errno));
-        }
-        if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) {
-            ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno));
-        }
-        if (displayId == mActiveDisplayId && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) {
-            mScheduler->disableHardwareVsync(displayId, true);
-            mScheduler->enableSyntheticVsync();
+
+        if (displayId == mActiveDisplayId) {
+            if (setSchedFifo(false) != NO_ERROR) {
+                ALOGW("Failed to set SCHED_OTHER after powering off active display: %s",
+                      strerror(errno));
+            }
+            if (setSchedAttr(false) != NO_ERROR) {
+                ALOGW("Failed set uclamp.min after powering off active display: %s",
+                      strerror(errno));
+            }
+
+            if (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND) {
+                mScheduler->disableHardwareVsync(displayId, true);
+                mScheduler->enableSyntheticVsync();
+            }
         }
 
         // Make sure HWVsync is disabled before turning off the display
@@ -6952,7 +6992,8 @@
 
     const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode);
 
-    if (capturingHdrLayers && !hintForSeamlessTransition) {
+    // TODO: Enable once HDR screenshots are ready.
+    if constexpr (/* DISABLES CODE */ (false)) {
         // For now since we only support 8-bit screenshots, just use HLG and
         // assume that 1.0 >= display max luminance. This isn't quite as future
         // proof as PQ is, but is good enough.
@@ -7943,6 +7984,7 @@
 
     onActiveDisplaySizeChanged(activeDisplay);
     mActiveDisplayTransformHint = activeDisplay.getTransformHint();
+    sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation());
 
     // The policy of the new active/pacesetter display may have changed while it was inactive. In
     // that case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In
@@ -8144,7 +8186,7 @@
                 });
     }
     if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
-        mDrawingState.traverseInZOrder([&refreshArgs, cursorOnly, &layers](Layer* layer) {
+        auto moveSnapshots = [&layers, &refreshArgs, cursorOnly](Layer* layer) {
             if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
                 if (cursorOnly &&
                     layer->getLayerSnapshot()->compositionType !=
@@ -8155,7 +8197,22 @@
                 refreshArgs.layers.push_back(layerFE);
                 layers.emplace_back(layer, layerFE.get());
             }
-        });
+        };
+
+        if (cursorOnly || !mVisibleRegionsDirty) {
+            // for hot path avoid traversals by walking though the previous composition list
+            for (sp<Layer> layer : mPreviouslyComposedLayers) {
+                moveSnapshots(layer.get());
+            }
+        } else {
+            mPreviouslyComposedLayers.clear();
+            mDrawingState.traverseInZOrder(
+                    [&moveSnapshots](Layer* layer) { moveSnapshots(layer); });
+            mPreviouslyComposedLayers.reserve(layers.size());
+            for (auto [layer, _] : layers) {
+                mPreviouslyComposedLayers.push_back(sp<Layer>::fromExisting(layer));
+            }
+        }
     }
 
     return layers;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index d92ec7a..e2691ab 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -296,8 +296,7 @@
     // the client can no longer modify this layer directly.
     void onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId);
 
-    // TODO: Remove atomic if move dtor to main thread CL lands
-    std::atomic<uint32_t> mNumClones;
+    std::vector<Layer*> mLayerMirrorRoots;
 
     TransactionCallbackInvoker& getTransactionCallbackInvoker() {
         return mTransactionCallbackInvoker;
@@ -332,6 +331,14 @@
     const DisplayDevice* getDisplayFromLayerStack(ui::LayerStack)
             REQUIRES(mStateLock, kMainThreadContext);
 
+    // TODO (b/259407931): Remove.
+    // TODO (b/281857977): This should be annotated with REQUIRES(kMainThreadContext), but this
+    // would require thread safety annotations throughout the frontend (in particular Layer and
+    // LayerFE).
+    static ui::Transform::RotationFlags getActiveDisplayRotationFlags() {
+        return sActiveDisplayRotationFlags;
+    }
+
 protected:
     // We're reference counted, never destroy SurfaceFlinger directly
     virtual ~SurfaceFlinger();
@@ -508,15 +515,13 @@
     }
 
     sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const;
-    status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                                 Vector<ComposerState>& state, const Vector<DisplayState>& displays,
-                                 uint32_t flags, const sp<IBinder>& applyToken,
-                                 InputWindowCommands inputWindowCommands,
-                                 int64_t desiredPresentTime, bool isAutoTimestamp,
-                                 const std::vector<client_cache_t>& uncacheBuffers,
-                                 bool hasListenerCallbacks,
-                                 const std::vector<ListenerCallbacks>& listenerCallbacks,
-                                 uint64_t transactionId) override;
+    status_t setTransactionState(
+            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
+            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
+            bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+            bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
+            uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) override;
     void bootFinished();
     virtual status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const;
     sp<IDisplayEventConnection> createDisplayEventConnection(
@@ -1199,6 +1204,11 @@
     std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithBuffersRemoved;
     // Tracks layers that need to update a display's dirty region.
     std::vector<sp<Layer>> mLayersPendingRefresh;
+    // Sorted list of layers that were composed during previous frame. This is used to
+    // avoid an expensive traversal of the layer hierarchy when there are no
+    // visible region changes. Because this is a list of strong pointers, this will
+    // extend the life of the layer but this list is only updated in the main thread.
+    std::vector<sp<Layer>> mPreviouslyComposedLayers;
 
     BootStage mBootStage = BootStage::BOOTLOADER;
 
@@ -1390,6 +1400,10 @@
 
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
+    // Must only be accessed on the main thread.
+    // TODO (b/259407931): Remove.
+    static ui::Transform::RotationFlags sActiveDisplayRotationFlags;
+
     bool isRefreshRateOverlayEnabled() const REQUIRES(mStateLock) {
         return hasDisplay(
                 [](const auto& display) { return display.isRefreshRateOverlayEnabled(); });
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index 57d927b..0694180 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -69,6 +69,13 @@
     for (auto& displayState : t.displays) {
         proto.mutable_display_changes()->Add(std::move(toProto(displayState)));
     }
+
+    proto.mutable_merged_transaction_ids()->Reserve(
+            static_cast<int32_t>(t.mergedTransactionIds.size()));
+    for (auto& mergedTransactionId : t.mergedTransactionIds) {
+        proto.mutable_merged_transaction_ids()->Add(mergedTransactionId);
+    }
+
     return proto;
 }
 
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 62a7dfd..7132a59 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -56,7 +56,8 @@
                      int64_t desiredPresentTime, bool isAutoTimestamp,
                      std::vector<uint64_t> uncacheBufferIds, int64_t postTime,
                      bool hasListenerCallbacks, std::vector<ListenerCallbacks> listenerCallbacks,
-                     int originPid, int originUid, uint64_t transactionId)
+                     int originPid, int originUid, uint64_t transactionId,
+                     std::vector<uint64_t> mergedTransactionIds)
           : frameTimelineInfo(frameTimelineInfo),
             states(std::move(composerStates)),
             displays(displayStates),
@@ -71,7 +72,8 @@
             listenerCallbacks(listenerCallbacks),
             originPid(originPid),
             originUid(originUid),
-            id(transactionId) {}
+            id(transactionId),
+            mergedTransactionIds(std::move(mergedTransactionIds)) {}
 
     // Invokes `void(const layer_state_t&)` visitor for matching layers.
     template <typename Visitor>
@@ -131,6 +133,7 @@
     int originUid;
     uint64_t id;
     bool sentFenceTimeoutWarning = false;
+    std::vector<uint64_t> mergedTransactionIds;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
index ce4d18f..80943b5 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -185,11 +185,12 @@
     bool hasListenerCallbacks = mFdp.ConsumeBool();
     std::vector<ListenerCallbacks> listenerCallbacks{};
     uint64_t transactionId = mFdp.ConsumeIntegral<uint64_t>();
+    std::vector<uint64_t> mergedTransactionIds{};
 
     mTestableFlinger.setTransactionState(FrameTimelineInfo{}, states, displays, flags, applyToken,
                                          InputWindowCommands{}, desiredPresentTime, isAutoTimestamp,
-                                         {}, hasListenerCallbacks, listenerCallbacks,
-                                         transactionId);
+                                         {}, hasListenerCallbacks, listenerCallbacks, transactionId,
+                                         mergedTransactionIds);
 }
 
 void SurfaceFlingerFuzzer::setDisplayStateLocked() {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 4d13aca..da5ec48 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -737,19 +737,18 @@
         return mFlinger->mTransactionHandler.mPendingTransactionQueues;
     }
 
-    auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                             Vector<ComposerState>& states, const Vector<DisplayState>& displays,
-                             uint32_t flags, const sp<IBinder>& applyToken,
-                             const InputWindowCommands& inputWindowCommands,
-                             int64_t desiredPresentTime, bool isAutoTimestamp,
-                             const std::vector<client_cache_t>& uncacheBuffers,
-                             bool hasListenerCallbacks,
-                             std::vector<ListenerCallbacks>& listenerCallbacks,
-                             uint64_t transactionId) {
+    auto setTransactionState(
+            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
+            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
+            bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+            bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
+            uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) {
         return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
                                              inputWindowCommands, desiredPresentTime,
                                              isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
-                                             listenerCallbacks, transactionId);
+                                             listenerCallbacks, transactionId,
+                                             mergedTransactionIds);
     }
 
     auto flushTransactionQueues() {
diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto
index 2c4eb10..b0cee9b 100644
--- a/services/surfaceflinger/layerproto/transactions.proto
+++ b/services/surfaceflinger/layerproto/transactions.proto
@@ -100,6 +100,7 @@
     uint64 transaction_id = 6;
     repeated LayerState layer_changes = 7;
     repeated DisplayState display_changes = 8;
+    repeated uint64 merged_transaction_ids = 9;
 }
 
 // Keep insync with layer_state_t
diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index 0495678..cf23169 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -91,7 +91,7 @@
     // Set uclamp.min setting on all threads, maybe an overkill but we want
     // to cover important threads like RenderEngine.
     if (SurfaceFlinger::setSchedAttr(true) != NO_ERROR) {
-        ALOGW("Couldn't set uclamp.min: %s\n", strerror(errno));
+        ALOGW("Failed to set uclamp.min during boot: %s", strerror(errno));
     }
 
     // The binder threadpool we start will inherit sched policy and priority
@@ -155,7 +155,7 @@
     startDisplayService(); // dependency on SF getting registered above
 
     if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) {
-        ALOGW("Couldn't set to SCHED_FIFO: %s", strerror(errno));
+        ALOGW("Failed to set SCHED_FIFO during boot: %s", strerror(errno));
     }
 
     // run surface flinger in this thread
diff --git a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
new file mode 100644
index 0000000..7077523
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2023 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 "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace {
+
+struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest {
+    static constexpr bool kWithMockScheduler = false;
+    ActiveDisplayRotationFlagsTest() : DisplayTransactionTest(kWithMockScheduler) {}
+
+    void SetUp() override {
+        injectMockScheduler(kInnerDisplayId);
+
+        // Inject inner and outer displays with uninitialized power modes.
+        constexpr bool kInitPowerMode = false;
+        {
+            InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+            auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
+            injector.setPowerMode(std::nullopt);
+            injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
+            mInnerDisplay = injector.inject();
+        }
+        {
+            OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+            auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
+            injector.setPowerMode(std::nullopt);
+            mOuterDisplay = injector.inject();
+        }
+
+        mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+        mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+
+        // The flags are a static variable, so by modifying them in the test, we
+        // are modifying the real ones used by SurfaceFlinger. Save the original
+        // flags so we can restore them on teardown. This isn't perfect - the
+        // phone may have been rotated during the test, so we're restoring the
+        // wrong flags. But if the phone is rotated, this may also fail the test.
+        mOldRotationFlags = mFlinger.mutableActiveDisplayRotationFlags();
+
+        // Reset to the expected default state.
+        mFlinger.mutableActiveDisplayRotationFlags() = ui::Transform::ROT_0;
+    }
+
+    void TearDown() override { mFlinger.mutableActiveDisplayRotationFlags() = mOldRotationFlags; }
+
+    static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
+    static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
+
+    sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
+    ui::Transform::RotationFlags mOldRotationFlags;
+};
+
+TEST_F(ActiveDisplayRotationFlagsTest, defaultRotation) {
+    ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotate90) {
+    auto displayToken = mInnerDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_90;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_90, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotate90_inactive) {
+    auto displayToken = mOuterDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_90;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_innerActive) {
+    auto displayToken = mInnerDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_180;
+
+    displayToken = mOuterDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_270;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_outerActive) {
+    mFlinger.mutableActiveDisplayId() = kOuterDisplayId;
+    auto displayToken = mInnerDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_180;
+
+    displayToken = mOuterDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_270;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, onActiveDisplayChanged) {
+    auto displayToken = mInnerDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_180;
+
+    displayToken = mOuterDisplay->getDisplayToken().promote();
+    mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+    mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+            ui::ROTATION_270;
+
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+    ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags());
+
+    mFlinger.onActiveDisplayChanged(mInnerDisplay.get(), *mOuterDisplay);
+    ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 55705ca..70f8a83 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -70,6 +70,7 @@
         ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
         "libsurfaceflinger_unittest_main.cpp",
+        "ActiveDisplayRotationFlagsTest.cpp",
         "CompositionTest.cpp",
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 6ca21bd..e8a9cfe 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -680,6 +680,9 @@
                 NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                      false);
         test->mFlinger.setLayerSidebandStream(layer, stream);
+        auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
+        layerDrawingState.crop =
+                Rect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH);
     }
 
     static void setupHwcSetSourceCropBufferCallExpectations(CompositionTest* test) {
@@ -814,6 +817,7 @@
         Mock::VerifyAndClear(test->mComposer);
 
         test->mFlinger.mutableDrawingState().layersSortedByZ.add(layer);
+        test->mFlinger.mutableVisibleRegionsDirty() = true;
     }
 
     static void cleanupInjectedLayers(CompositionTest* test) {
@@ -822,6 +826,7 @@
 
         test->mDisplay->getCompositionDisplay()->clearOutputLayers();
         test->mFlinger.mutableDrawingState().layersSortedByZ.clear();
+        test->mFlinger.mutablePreviouslyComposedLayers().clear();
 
         // Layer should be unregistered with scheduler.
         test->mFlinger.commit();
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index a189c00..945e488 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -466,19 +466,18 @@
         return mFlinger->mTransactionHandler.mPendingTransactionCount.load();
     }
 
-    auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                             Vector<ComposerState>& states, const Vector<DisplayState>& displays,
-                             uint32_t flags, const sp<IBinder>& applyToken,
-                             const InputWindowCommands& inputWindowCommands,
-                             int64_t desiredPresentTime, bool isAutoTimestamp,
-                             const std::vector<client_cache_t>& uncacheBuffers,
-                             bool hasListenerCallbacks,
-                             std::vector<ListenerCallbacks>& listenerCallbacks,
-                             uint64_t transactionId) {
+    auto setTransactionState(
+            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
+            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
+            bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+            bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
+            uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) {
         return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
                                              inputWindowCommands, desiredPresentTime,
                                              isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
-                                             listenerCallbacks, transactionId);
+                                             listenerCallbacks, transactionId,
+                                             mergedTransactionIds);
     }
 
     auto setTransactionStateInternal(TransactionState& transaction) {
@@ -588,6 +587,7 @@
     auto& mutablePhysicalDisplays() { return mFlinger->mPhysicalDisplays; }
     auto& mutableDrawingState() { return mFlinger->mDrawingState; }
     auto& mutableGeometryDirty() { return mFlinger->mGeometryDirty; }
+    auto& mutableVisibleRegionsDirty() { return mFlinger->mVisibleRegionsDirty; }
     auto& mutableMainThreadId() { return mFlinger->mMainThreadId; }
     auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; }
     auto& mutableTexturePool() { return mFlinger->mTexturePool; }
@@ -599,6 +599,11 @@
     auto& mutableHwcPhysicalDisplayIdMap() { return getHwComposer().mPhysicalDisplayIdMap; }
     auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; }
     auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; }
+    auto& mutablePreviouslyComposedLayers() { return mFlinger->mPreviouslyComposedLayers; }
+
+    auto& mutableActiveDisplayRotationFlags() {
+        return SurfaceFlinger::sActiveDisplayRotationFlags;
+    }
 
     auto fromHandle(const sp<IBinder>& handle) { return LayerHandle::getLayer(handle); }
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 6a641b3..afb8efb 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -73,6 +73,7 @@
         FrameTimelineInfo frameTimelineInfo;
         std::vector<client_cache_t> uncacheBuffers;
         uint64_t id = static_cast<uint64_t>(-1);
+        std::vector<uint64_t> mergedTransactionIds;
         static_assert(0xffffffffffffffff == static_cast<uint64_t>(-1));
     };
 
@@ -108,7 +109,7 @@
                                      transaction.applyToken, transaction.inputWindowCommands,
                                      transaction.desiredPresentTime, transaction.isAutoTimestamp,
                                      transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
-                                     transaction.id);
+                                     transaction.id, transaction.mergedTransactionIds);
 
         // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for
         // SF to commit the transaction. If this is animation, it should not time out waiting.
@@ -135,7 +136,7 @@
                                      transaction.applyToken, transaction.inputWindowCommands,
                                      transaction.desiredPresentTime, transaction.isAutoTimestamp,
                                      transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
-                                     transaction.id);
+                                     transaction.id, transaction.mergedTransactionIds);
 
         nsecs_t returnedTime = systemTime();
         EXPECT_LE(returnedTime, applicationSentTime + TRANSACTION_TIMEOUT);
@@ -166,7 +167,7 @@
                                      transactionA.applyToken, transactionA.inputWindowCommands,
                                      transactionA.desiredPresentTime, transactionA.isAutoTimestamp,
                                      transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
-                                     transactionA.id);
+                                     transactionA.id, transactionA.mergedTransactionIds);
 
         // This thread should not have been blocked by the above transaction
         // (5s is the timeout period that applyTransactionState waits for SF to
@@ -181,7 +182,7 @@
                                      transactionB.applyToken, transactionB.inputWindowCommands,
                                      transactionB.desiredPresentTime, transactionB.isAutoTimestamp,
                                      transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
-                                     transactionB.id);
+                                     transactionB.id, transactionB.mergedTransactionIds);
 
         // this thread should have been blocked by the above transaction
         // if this is an animation, this thread should be blocked for 5s
@@ -218,7 +219,8 @@
                                  transactionA.displays, transactionA.flags, transactionA.applyToken,
                                  transactionA.inputWindowCommands, transactionA.desiredPresentTime,
                                  transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
-                                 mHasListenerCallbacks, mCallbacks, transactionA.id);
+                                 mHasListenerCallbacks, mCallbacks, transactionA.id,
+                                 transactionA.mergedTransactionIds);
 
     auto& transactionQueue = mFlinger.getTransactionQueue();
     ASSERT_FALSE(transactionQueue.isEmpty());
@@ -238,7 +240,8 @@
                                  transactionA.displays, transactionA.flags, transactionA.applyToken,
                                  transactionA.inputWindowCommands, transactionA.desiredPresentTime,
                                  transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
-                                 mHasListenerCallbacks, mCallbacks, transactionA.id);
+                                 mHasListenerCallbacks, mCallbacks, transactionA.id,
+                                 transactionA.mergedTransactionIds);
 
     auto& transactionQueue = mFlinger.getTransactionQueue();
     ASSERT_FALSE(transactionQueue.isEmpty());
@@ -251,7 +254,8 @@
     mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags,
                                  empty.applyToken, empty.inputWindowCommands,
                                  empty.desiredPresentTime, empty.isAutoTimestamp,
-                                 empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id);
+                                 empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id,
+                                 empty.mergedTransactionIds);
 
     // flush transaction queue should flush as desiredPresentTime has
     // passed
@@ -388,7 +392,8 @@
                                               transaction.desiredPresentTime,
                                               transaction.isAutoTimestamp, {}, systemTime(),
                                               mHasListenerCallbacks, mCallbacks, getpid(),
-                                              static_cast<int>(getuid()), transaction.id);
+                                              static_cast<int>(getuid()), transaction.id,
+                                              transaction.mergedTransactionIds);
             mFlinger.setTransactionStateInternal(transactionState);
         }
         mFlinger.flushTransactionQueues();
@@ -1083,4 +1088,137 @@
     EXPECT_EQ(transactionsReadyToBeApplied.front().id, 42u);
 }
 
+TEST(TransactionHandlerTest, TransactionsKeepTrackOfDirectMerges) {
+    SurfaceComposerClient::Transaction transaction1, transaction2, transaction3, transaction4;
+
+    uint64_t transaction2Id = transaction2.getId();
+    uint64_t transaction3Id = transaction3.getId();
+    EXPECT_NE(transaction2Id, transaction3Id);
+
+    transaction1.merge(std::move(transaction2));
+    transaction1.merge(std::move(transaction3));
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 2u);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction3Id);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[1], transaction2Id);
+}
+
+TEST(TransactionHandlerTest, TransactionsKeepTrackOfIndirectMerges) {
+    SurfaceComposerClient::Transaction transaction1, transaction2, transaction3, transaction4;
+
+    uint64_t transaction2Id = transaction2.getId();
+    uint64_t transaction3Id = transaction3.getId();
+    uint64_t transaction4Id = transaction4.getId();
+    EXPECT_NE(transaction2Id, transaction3Id);
+    EXPECT_NE(transaction2Id, transaction4Id);
+    EXPECT_NE(transaction3Id, transaction4Id);
+
+    transaction4.merge(std::move(transaction2));
+    transaction4.merge(std::move(transaction3));
+
+    EXPECT_EQ(transaction4.getMergedTransactionIds().size(), 2u);
+    EXPECT_EQ(transaction4.getMergedTransactionIds()[0], transaction3Id);
+    EXPECT_EQ(transaction4.getMergedTransactionIds()[1], transaction2Id);
+
+    transaction1.merge(std::move(transaction4));
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 3u);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction4Id);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[1], transaction3Id);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[2], transaction2Id);
+}
+
+TEST(TransactionHandlerTest, TransactionMergesAreCleared) {
+    SurfaceComposerClient::Transaction transaction1, transaction2, transaction3;
+
+    transaction1.merge(std::move(transaction2));
+    transaction1.merge(std::move(transaction3));
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 2u);
+
+    transaction1.clear();
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().empty(), true);
+}
+
+TEST(TransactionHandlerTest, TransactionMergesAreCapped) {
+    SurfaceComposerClient::Transaction transaction;
+    std::vector<uint64_t> mergedTransactionIds;
+
+    for (uint i = 0; i < 20u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        mergedTransactionIds.push_back(transactionToMerge.getId());
+        transaction.merge(std::move(transactionToMerge));
+    }
+
+    // Keeps latest 10 merges in order of merge recency
+    EXPECT_EQ(transaction.getMergedTransactionIds().size(), 10u);
+    for (uint i = 0; i < 10u; i++) {
+        EXPECT_EQ(transaction.getMergedTransactionIds()[i],
+                  mergedTransactionIds[mergedTransactionIds.size() - 1 - i]);
+    }
+}
+
+TEST(TransactionHandlerTest, KeepsMergesFromMoreRecentMerge) {
+    SurfaceComposerClient::Transaction transaction1, transaction2, transaction3;
+    std::vector<uint64_t> mergedTransactionIds1, mergedTransactionIds2, mergedTransactionIds3;
+    uint64_t transaction2Id = transaction2.getId();
+    uint64_t transaction3Id = transaction3.getId();
+
+    for (uint i = 0; i < 20u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        mergedTransactionIds1.push_back(transactionToMerge.getId());
+        transaction1.merge(std::move(transactionToMerge));
+    }
+
+    for (uint i = 0; i < 5u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        mergedTransactionIds2.push_back(transactionToMerge.getId());
+        transaction2.merge(std::move(transactionToMerge));
+    }
+
+    transaction1.merge(std::move(transaction2));
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction2Id);
+    for (uint i = 0; i < 5u; i++) {
+        EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 1u],
+                  mergedTransactionIds2[mergedTransactionIds2.size() - 1 - i]);
+    }
+    for (uint i = 0; i < 4u; i++) {
+        EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 6u],
+                  mergedTransactionIds1[mergedTransactionIds1.size() - 1 - i]);
+    }
+
+    for (uint i = 0; i < 20u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        mergedTransactionIds3.push_back(transactionToMerge.getId());
+        transaction3.merge(std::move(transactionToMerge));
+    }
+
+    transaction1.merge(std::move(transaction3));
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u);
+    EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction3Id);
+    for (uint i = 0; i < 9u; i++) {
+        EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 1],
+                  mergedTransactionIds3[mergedTransactionIds3.size() - 1 - i]);
+    }
+}
+
+TEST(TransactionHandlerTest, CanAddTransactionWithFullMergedIds) {
+    SurfaceComposerClient::Transaction transaction1, transaction2;
+    for (uint i = 0; i < 20u; i++) {
+        SurfaceComposerClient::Transaction transactionToMerge;
+        transaction1.merge(std::move(transactionToMerge));
+    }
+
+    EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u);
+
+    auto transaction1Id = transaction1.getId();
+    transaction2.merge(std::move(transaction1));
+    EXPECT_EQ(transaction2.getMergedTransactionIds().size(), 10u);
+    auto mergedTransactionIds = transaction2.getMergedTransactionIds();
+    EXPECT_TRUE(std::count(mergedTransactionIds.begin(), mergedTransactionIds.end(),
+                           transaction1Id) > 0);
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 82aac7e..92411a7 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -67,8 +67,14 @@
         ASSERT_EQ(actualProto.transactions().size(),
                   static_cast<int32_t>(expectedTransactions.size()));
         for (uint32_t i = 0; i < expectedTransactions.size(); i++) {
-            EXPECT_EQ(actualProto.transactions(static_cast<int32_t>(i)).pid(),
-                      expectedTransactions[i].originPid);
+            const auto expectedTransaction = expectedTransactions[i];
+            const auto protoTransaction = actualProto.transactions(static_cast<int32_t>(i));
+            EXPECT_EQ(protoTransaction.transaction_id(), expectedTransaction.id);
+            EXPECT_EQ(protoTransaction.pid(), expectedTransaction.originPid);
+            for (uint32_t i = 0; i < expectedTransaction.mergedTransactionIds.size(); i++) {
+                EXPECT_EQ(protoTransaction.merged_transaction_ids(static_cast<int32_t>(i)),
+                          expectedTransaction.mergedTransactionIds[i]);
+            }
         }
     }
 
@@ -92,6 +98,7 @@
         TransactionState transaction;
         transaction.id = i;
         transaction.originPid = static_cast<int32_t>(i);
+        transaction.mergedTransactionIds = std::vector<uint64_t>{i + 100, i + 102};
         transactions.emplace_back(transaction);
         mTracing.addQueuedTransaction(transaction);
     }