Merge "Refactor RenderAreaFuture to use RenderAreaBuilder" into main
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index 01f523b..bec3283 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -17,8 +17,20 @@
 /**
  * @addtogroup Choreographer
  *
- * Choreographer coordinates the timing of frame rendering. This is the C version of the
- * android.view.Choreographer object in Java.
+ * Choreographer coordinates the timing of frame rendering. This is the C
+ * version of the android.view.Choreographer object in Java. If you do not use
+ * Choreographer to pace your render loop, you may render too quickly for the
+ * display, increasing latency between frame submission and presentation.
+ *
+ * Input events are guaranteed to be processed before the frame callback is
+ * called, and will not be run concurrently. Input and sensor events should not
+ * be handled in the Choregrapher callback.
+ *
+ * The frame callback is also the appropriate place to run any per-frame state
+ * update logic. For example, in a game, the frame callback should be
+ * responsible for updating things like physics, AI, game state, and rendering
+ * the frame. Input and sensors should be handled separately via callbacks
+ * registered with AInputQueue and ASensorManager.
  *
  * As of API level 33, apps can follow proper frame pacing and even choose a future frame to render.
  * The API is used as follows:
@@ -38,6 +50,11 @@
  * 4. SurfaceFlinger attempts to follow the chosen frame timeline, by not applying transactions or
  * latching buffers before the desired presentation time.
  *
+ * On older devices, AChoreographer_postFrameCallback64 or
+ * AChoreographer_postFrameCallback can be used to lesser effect. They cannot be
+ * used to precisely plan your render timeline, but will rate limit to avoid
+ * overloading the display pipeline and increasing frame latency.
+ *
  * @{
  */
 
@@ -129,14 +146,46 @@
 AChoreographer* AChoreographer_getInstance() __INTRODUCED_IN(24);
 
 /**
- * Deprecated: Use AChoreographer_postFrameCallback64 instead.
+ * Post a callback to be run when the application should begin rendering the
+ * next frame. The data pointer provided will be passed to the callback function
+ * when it's called.
+ *
+ * The callback will only be run for the next frame, not all subsequent frames,
+ * so to render continuously the callback should itself call
+ * AChoreographer_postFrameCallback.
+ *
+ * \bug The callback receives the frame time in nanoseconds as a long. On 32-bit
+ * systems, long is 32-bit, so the frame time will roll over roughly every two
+ * seconds. If your minSdkVersion is 29 or higher, switch to
+ * AChoreographer_postFrameCallback64, which uses a 64-bit frame time for all
+ * platforms. For older OS versions, you must combine the argument with the
+ * upper bits of clock_gettime(CLOCK_MONOTONIC, ...) on 32-bit systems.
+ *
+ * \deprecated Use AChoreographer_postFrameCallback64, which does not have the
+ * bug described above.
  */
 void AChoreographer_postFrameCallback(AChoreographer* choreographer,
                                       AChoreographer_frameCallback callback, void* data)
         __INTRODUCED_IN(24) __DEPRECATED_IN(29, "Use AChoreographer_postFrameCallback64 instead");
 
 /**
- * Deprecated: Use AChoreographer_postFrameCallbackDelayed64 instead.
+ * Post a callback to be run when the application should begin rendering the
+ * next frame following the specified delay. The data pointer provided will be
+ * passed to the callback function when it's called.
+ *
+ * The callback will only be run for the next frame after the delay, not all
+ * subsequent frames, so to render continuously the callback should itself call
+ * AChoreographer_postFrameCallbackDelayed.
+ *
+ * \bug The callback receives the frame time in nanoseconds as a long. On 32-bit
+ * systems, long is 32-bit, so the frame time will roll over roughly every two
+ * seconds. If your minSdkVersion is 29 or higher, switch to
+ * AChoreographer_postFrameCallbackDelayed64, which uses a 64-bit frame time for
+ * all platforms. For older OS versions, you must combine the argument with the
+ * upper bits of clock_gettime(CLOCK_MONOTONIC, ...) on 32-bit systems.
+ *
+ * \deprecated Use AChoreographer_postFrameCallbackDelayed64, which does not
+ * have the bug described above.
  */
 void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer,
                                              AChoreographer_frameCallback callback, void* data,
@@ -144,8 +193,13 @@
         __DEPRECATED_IN(29, "Use AChoreographer_postFrameCallbackDelayed64 instead");
 
 /**
- * Post a callback to be run on the next frame.  The data pointer provided will
- * be passed to the callback function when it's called.
+ * Post a callback to be run when the application should begin rendering the
+ * next frame. The data pointer provided will be passed to the callback function
+ * when it's called.
+ *
+ * The callback will only be run on the next frame, not all subsequent frames,
+ * so to render continuously the callback should itself call
+ * AChoreographer_postFrameCallback64.
  *
  * Available since API level 29.
  */
@@ -154,9 +208,13 @@
         __INTRODUCED_IN(29);
 
 /**
- * Post a callback to be run on the frame following the specified delay.  The
- * data pointer provided will be passed to the callback function when it's
- * called.
+ * Post a callback to be run when the application should begin rendering the
+ * next frame following the specified delay. The data pointer provided will be
+ * passed to the callback function when it's called.
+ *
+ * The callback will only be run for the next frame after the delay, not all
+ * subsequent frames, so to render continuously the callback should itself call
+ * AChoreographer_postFrameCallbackDelayed64.
  *
  * Available since API level 29.
  */
@@ -165,8 +223,13 @@
                                                uint32_t delayMillis) __INTRODUCED_IN(29);
 
 /**
- * Posts a callback to be run on the next frame. The data pointer provided will
- * be passed to the callback function when it's called.
+ * Posts a callback to be run when the application should begin rendering the
+ * next frame. The data pointer provided will be passed to the callback function
+ * when it's called.
+ *
+ * The callback will only be run for the next frame, not all subsequent frames,
+ * so to render continuously the callback should itself call
+ * AChoreographer_postVsyncCallback.
  *
  * Available since API level 33.
  */
diff --git a/include/android/looper.h b/include/android/looper.h
index 6a02916..d80a366 100644
--- a/include/android/looper.h
+++ b/include/android/looper.h
@@ -182,23 +182,27 @@
  * If the timeout is zero, returns immediately without blocking.
  * If the timeout is negative, waits indefinitely until an event appears.
  *
- * Returns ALOOPER_POLL_WAKE if the poll was awoken using wake() before
+ * Returns ALOOPER_POLL_WAKE if the poll was awoken using ALooper_wake() before
  * the timeout expired and no callbacks were invoked and no other file
- * descriptors were ready.
+ * descriptors were ready. **All return values may also imply
+ * ALOOPER_POLL_WAKE.**
  *
- * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked.
+ * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked. The poll
+ * may also have been explicitly woken by ALooper_wake.
  *
- * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
- * timeout expired.
+ * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given timeout
+ * expired. The poll may also have been explicitly woken by ALooper_wake.
  *
- * Returns ALOOPER_POLL_ERROR if an error occurred.
+ * Returns ALOOPER_POLL_ERROR if the calling thread has no associated Looper or
+ * for unrecoverable internal errors. The poll may also have been explicitly
+ * woken by ALooper_wake.
  *
- * Returns a value >= 0 containing an identifier (the same identifier
- * `ident` passed to ALooper_addFd()) if its file descriptor has data
- * and it has no callback function (requiring the caller here to
- * handle it).  In this (and only this) case outFd, outEvents and
- * outData will contain the poll events and data associated with the
- * fd, otherwise they will be set to NULL.
+ * Returns a value >= 0 containing an identifier (the same identifier `ident`
+ * passed to ALooper_addFd()) if its file descriptor has data and it has no
+ * callback function (requiring the caller here to handle it).  In this (and
+ * only this) case outFd, outEvents and outData will contain the poll events and
+ * data associated with the fd, otherwise they will be set to NULL. The poll may
+ * also have been explicitly woken by ALooper_wake.
  *
  * This method does not return until it has finished invoking the appropriate callbacks
  * for all file descriptors that were signalled.
@@ -210,11 +214,21 @@
  * data has been consumed or a file descriptor is available with no callback.
  * This function will never return ALOOPER_POLL_CALLBACK.
  *
- * Removed in API 34 as ALooper_pollAll can swallow ALooper_wake calls.
- * Use ALooper_pollOnce instead.
+ * This API cannot be used safely, but a safe alternative exists (see below). As
+ * such, new builds will not be able to call this API and must migrate to the
+ * safe API. Binary compatibility is preserved to support already-compiled apps.
+ *
+ * \bug ALooper_pollAll will not wake in response to ALooper_wake calls if it
+ * also handles another event at the same time.
+ *
+ * \deprecated Calls to ALooper_pollAll should be replaced with
+ * ALooper_pollOnce. If you call ALooper_pollOnce in a loop, you *must* treat
+ * all return values as if they also indicate ALOOPER_POLL_WAKE.
  */
 int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData)
-    __REMOVED_IN(1, "ALooper_pollAll may ignore wakes. Use ALooper_pollOnce instead. See https://github.com/android/ndk/discussions/2020 for more information");
+        __REMOVED_IN(1,
+                     "ALooper_pollAll may ignore wakes. Use ALooper_pollOnce instead. See "
+                     "The API documentation for more information");
 
 /**
  * Wakes the poll asynchronously.
diff --git a/include/ftl/details/hash.h b/include/ftl/details/hash.h
index f9a7fa8..230ae51 100644
--- a/include/ftl/details/hash.h
+++ b/include/ftl/details/hash.h
@@ -43,6 +43,7 @@
   return v ^ (v >> 47);
 }
 
+__attribute__((no_sanitize("unsigned-integer-overflow")))
 constexpr std::uint64_t hash_length_16(std::uint64_t u, std::uint64_t v) {
   constexpr std::uint64_t kPrime = 0x9ddfea08eb382d69ull;
   auto a = (u ^ v) * kPrime;
@@ -58,6 +59,7 @@
 constexpr std::uint64_t kPrime2 = 0x9ae16a3b2f90404full;
 constexpr std::uint64_t kPrime3 = 0xc949d7c7509e6557ull;
 
+__attribute__((no_sanitize("unsigned-integer-overflow")))
 inline std::uint64_t hash_length_0_to_16(const char* str, std::uint64_t length) {
   if (length > 8) {
     const auto a = read_unaligned(str);
@@ -80,6 +82,7 @@
   return kPrime2;
 }
 
+__attribute__((no_sanitize("unsigned-integer-overflow")))
 inline std::uint64_t hash_length_17_to_32(const char* str, std::uint64_t length) {
   const auto a = read_unaligned(str) * kPrime1;
   const auto b = read_unaligned(str + 8);
@@ -89,6 +92,7 @@
                         a + rotate(b ^ kPrime3, 20) - c + length);
 }
 
+__attribute__((no_sanitize("unsigned-integer-overflow")))
 inline std::uint64_t hash_length_33_to_64(const char* str, std::uint64_t length) {
   auto z = read_unaligned(str + 24);
   auto a = read_unaligned(str) + (length + read_unaligned(str + length - 16)) * kPrime0;
diff --git a/libs/cputimeinstate/testtimeinstate.cpp b/libs/cputimeinstate/testtimeinstate.cpp
index 6ccc6ca..81f6a58 100644
--- a/libs/cputimeinstate/testtimeinstate.cpp
+++ b/libs/cputimeinstate/testtimeinstate.cpp
@@ -40,6 +40,9 @@
 static constexpr uint64_t NSEC_PER_SEC = 1000000000;
 static constexpr uint64_t NSEC_PER_YEAR = NSEC_PER_SEC * 60 * 60 * 24 * 365;
 
+// Declare busy loop variable globally to prevent removal during optimization
+static long sum __attribute__((used)) = 0;
+
 using std::vector;
 
 class TimeInStateTest : public testing::Test {
@@ -576,7 +579,7 @@
 
 // Keeps CPU busy with some number crunching
 void useCpu() {
-    long sum = 0;
+    sum = 0;
     for (int i = 0; i < 100000; i++) {
         sum *= i;
     }
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 4dc2737..a3d0c2b 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -17,7 +17,12 @@
 #define LOG_TAG "PointerChoreographer"
 
 #include <android-base/logging.h>
+#include <com_android_input_flags.h>
+#if defined(__ANDROID__)
+#include <gui/SurfaceComposerClient.h>
+#endif
 #include <input/PrintTools.h>
+#include <unordered_set>
 
 #include "PointerChoreographer.h"
 
@@ -25,6 +30,10 @@
 
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+static const bool HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS =
+        input_flags::hide_pointer_indicators_for_secure_windows();
+
 namespace {
 
 bool isFromMouse(const NotifyMotionArgs& args) {
@@ -96,6 +105,14 @@
         mShowTouchesEnabled(false),
         mStylusPointerIconEnabled(false) {}
 
+PointerChoreographer::~PointerChoreographer() {
+    std::scoped_lock _l(mLock);
+    if (mWindowInfoListener == nullptr) {
+        return;
+    }
+    mWindowInfoListener->onPointerChoreographerDestroyed();
+}
+
 void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
     PointerDisplayChange pointerDisplayChange;
 
@@ -231,6 +248,7 @@
     auto [it, _] = mDrawingTabletPointersByDevice.try_emplace(args.deviceId,
                                                               getMouseControllerConstructor(
                                                                       args.displayId));
+    // TODO (b/325252005): Add handing for drawing tablets mouse pointer controller
 
     PointerControllerInterface& pc = *it->second;
 
@@ -268,7 +286,11 @@
     }
 
     // Get the touch pointer controller for the device, or create one if it doesn't exist.
-    auto [it, _] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+    auto [it, controllerAdded] =
+            mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+    if (controllerAdded) {
+        onControllerAddedOrRemoved();
+    }
 
     PointerControllerInterface& pc = *it->second;
 
@@ -306,6 +328,7 @@
     auto [it, _] =
             mStylusPointersByDevice.try_emplace(args.deviceId,
                                                 getStylusControllerConstructor(args.displayId));
+    // TODO (b/325252005): Add handing for stylus pointer controller
 
     PointerControllerInterface& pc = *it->second;
 
@@ -345,6 +368,31 @@
     mTouchPointersByDevice.erase(args.deviceId);
     mStylusPointersByDevice.erase(args.deviceId);
     mDrawingTabletPointersByDevice.erase(args.deviceId);
+    onControllerAddedOrRemoved();
+}
+
+void PointerChoreographer::onControllerAddedOrRemoved() {
+    if (!HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS) {
+        return;
+    }
+    bool requireListener = !mTouchPointersByDevice.empty();
+    // TODO (b/325252005): Update for other types of pointer controllers
+
+    if (requireListener && mWindowInfoListener == nullptr) {
+        mWindowInfoListener = sp<PointerChoreographerDisplayInfoListener>::make(this);
+        auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
+                                          std::vector<android::gui::DisplayInfo>{});
+#if defined(__ANDROID__)
+        SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener,
+                                                                    &initialInfo);
+#endif
+        onWindowInfosChangedLocked(initialInfo.first);
+    } else if (!requireListener && mWindowInfoListener != nullptr) {
+#if defined(__ANDROID__)
+        SurfaceComposerClient::getDefault()->removeWindowInfosListener(mWindowInfoListener);
+#endif
+        mWindowInfoListener = nullptr;
+    }
 }
 
 void PointerChoreographer::notifyPointerCaptureChanged(
@@ -358,6 +406,12 @@
     mNextListener.notify(args);
 }
 
+void PointerChoreographer::onWindowInfosChanged(
+        const std::vector<android::gui::WindowInfo>& windowInfos) {
+    std::scoped_lock _l(mLock);
+    onWindowInfosChangedLocked(windowInfos);
+}
+
 void PointerChoreographer::dump(std::string& dump) {
     std::scoped_lock _l(mLock);
 
@@ -410,6 +464,7 @@
     if (it == mMousePointersByDisplay.end()) {
         it = mMousePointersByDisplay.emplace(displayId, getMouseControllerConstructor(displayId))
                      .first;
+        // TODO (b/325252005): Add handing for mouse pointer controller
     }
 
     return {displayId, *it->second};
@@ -450,6 +505,8 @@
             auto [mousePointerIt, isNewMousePointer] =
                     mMousePointersByDisplay.try_emplace(displayId,
                                                         getMouseControllerConstructor(displayId));
+            // TODO (b/325252005): Add handing for mouse pointer controller
+
             mMouseDevices.emplace(info.getId());
             if ((!isKnownMouse || isNewMousePointer) && canUnfadeOnDisplay(displayId)) {
                 mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE);
@@ -488,6 +545,8 @@
                 mInputDeviceInfos.end();
     });
 
+    onControllerAddedOrRemoved();
+
     // Check if we need to notify the policy if there's a change on the pointer display ID.
     return calculatePointerDisplayChangeToNotify();
 }
@@ -652,6 +711,31 @@
     return false;
 }
 
+void PointerChoreographer::onWindowInfosChangedLocked(
+        const std::vector<android::gui::WindowInfo>& windowInfos) {
+    // Mark all spot controllers secure on displays containing secure windows and
+    // remove secure flag from others if required
+    std::unordered_set<int32_t> privacySensitiveDisplays;
+    std::unordered_set<int32_t> allDisplayIds;
+    for (const auto& windowInfo : windowInfos) {
+        allDisplayIds.insert(windowInfo.displayId);
+        if (!windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE) &&
+            windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)) {
+            privacySensitiveDisplays.insert(windowInfo.displayId);
+        }
+    }
+
+    for (auto& it : mTouchPointersByDevice) {
+        auto& pc = it.second;
+        for (int32_t displayId : allDisplayIds) {
+            pc->setSkipScreenshot(displayId,
+                                  privacySensitiveDisplays.find(displayId) !=
+                                          privacySensitiveDisplays.end());
+        }
+    }
+    // TODO (b/325252005): update skip screenshot flag for other types of pointer controllers
+}
+
 void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visible) {
     std::scoped_lock lock(mLock);
     if (visible) {
@@ -702,4 +786,18 @@
     return ConstructorDelegate(std::move(ctor));
 }
 
+void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(
+        const gui::WindowInfosUpdate& windowInfosUpdate) {
+    std::scoped_lock _l(mListenerLock);
+    if (mPointerChoreographer != nullptr) {
+        mPointerChoreographer->onWindowInfosChanged(windowInfosUpdate.windowInfos);
+    }
+}
+
+void PointerChoreographer::PointerChoreographerDisplayInfoListener::
+        onPointerChoreographerDestroyed() {
+    std::scoped_lock _l(mListenerLock);
+    mPointerChoreographer = nullptr;
+}
+
 } // namespace android
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index a3c210e..b29d9cd 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -21,6 +21,7 @@
 #include "PointerChoreographerPolicyInterface.h"
 
 #include <android-base/thread_annotations.h>
+#include <gui/WindowInfosListener.h>
 #include <type_traits>
 
 namespace android {
@@ -83,7 +84,7 @@
 public:
     explicit PointerChoreographer(InputListenerInterface& listener,
                                   PointerChoreographerPolicyInterface&);
-    ~PointerChoreographer() override = default;
+    ~PointerChoreographer() override;
 
     void setDefaultMouseDisplayId(int32_t displayId) override;
     void setDisplayViewports(const std::vector<DisplayViewport>& viewports) override;
@@ -106,6 +107,9 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
+    // Public because it's also used by tests to simulate the WindowInfosListener callback
+    void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>& windowInfos);
+
     void dump(std::string& dump) override;
 
 private:
@@ -127,6 +131,22 @@
     void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processDeviceReset(const NotifyDeviceResetArgs& args);
+    void onControllerAddedOrRemoved() REQUIRES(mLock);
+    void onWindowInfosChangedLocked(const std::vector<android::gui::WindowInfo>& windowInfos)
+            REQUIRES(mLock);
+
+    class PointerChoreographerDisplayInfoListener : public gui::WindowInfosListener {
+    public:
+        explicit PointerChoreographerDisplayInfoListener(PointerChoreographer* pc)
+              : mPointerChoreographer(pc){};
+        void onWindowInfosChanged(const gui::WindowInfosUpdate&) override;
+        void onPointerChoreographerDestroyed();
+
+    private:
+        std::mutex mListenerLock;
+        PointerChoreographer* mPointerChoreographer GUARDED_BY(mListenerLock);
+    };
+    sp<PointerChoreographerDisplayInfoListener> mWindowInfoListener GUARDED_BY(mLock);
 
     using ControllerConstructor =
             ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index de841ba..79b8560 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -5363,31 +5363,32 @@
         onFocusChangedLocked(*changes, traceContext.getTracker(), removedFocusedWindowHandle);
     }
 
-    std::unordered_map<int32_t, TouchState>::iterator stateIt =
-            mTouchStatesByDisplay.find(displayId);
-    if (stateIt != mTouchStatesByDisplay.end()) {
-        TouchState& state = stateIt->second;
+    if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
+        TouchState& state = it->second;
         for (size_t i = 0; i < state.windows.size();) {
             TouchedWindow& touchedWindow = state.windows[i];
-            if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) {
-                LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
-                          << " in display %" << displayId;
-                CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                           "touched window was removed", traceContext.getTracker());
-                synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
-                // Since we are about to drop the touch, cancel the events for the wallpaper as
-                // well.
-                if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) &&
-                    touchedWindow.windowHandle->getInfo()->inputConfig.test(
-                            gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
-                    if (const auto& ww = state.getWallpaperWindow(); ww) {
+            if (getWindowHandleLocked(touchedWindow.windowHandle) != nullptr) {
+                i++;
+                continue;
+            }
+            LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
+                      << " in display %" << displayId;
+            CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+                                       "touched window was removed", traceContext.getTracker());
+            synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
+            // Since we are about to drop the touch, cancel the events for the wallpaper as
+            // well.
+            if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+                touchedWindow.windowHandle->getInfo()->inputConfig.test(
+                        gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
+                for (const DeviceId deviceId : touchedWindow.getTouchingDeviceIds()) {
+                    if (const auto& ww = state.getWallpaperWindow(deviceId); ww != nullptr) {
+                        options.deviceId = deviceId;
                         synthesizeCancelationEventsForWindowLocked(ww, options);
                     }
                 }
-                state.windows.erase(state.windows.begin() + i);
-            } else {
-                ++i;
             }
+            state.windows.erase(state.windows.begin() + i);
         }
 
         // If drag window is gone, it would receive a cancel event and broadcast the DRAG_END. We
@@ -5662,13 +5663,13 @@
             ALOGD("Touch transfer failed because from window is not being touched.");
             return false;
         }
-        std::set<int32_t> deviceIds = touchedWindow->getTouchingDeviceIds();
+        std::set<DeviceId> deviceIds = touchedWindow->getTouchingDeviceIds();
         if (deviceIds.size() != 1) {
             LOG(INFO) << "Can't transfer touch. Currently touching devices: " << dumpSet(deviceIds)
                       << " for window: " << touchedWindow->dump();
             return false;
         }
-        const int32_t deviceId = *deviceIds.begin();
+        const DeviceId deviceId = *deviceIds.begin();
 
         const sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
         const sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId);
@@ -5722,13 +5723,18 @@
                                        "transferring touch from this window to another window",
                                        traceContext.getTracker());
             synthesizeCancelationEventsForWindowLocked(fromWindowHandle, options, fromConnection);
-            synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection,
-                                                           newTargetFlags,
-                                                           traceContext.getTracker());
 
             // Check if the wallpaper window should deliver the corresponding event.
             transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
                                    *state, deviceId, pointers, traceContext.getTracker());
+
+            // Because new window may have a wallpaper window, it will merge input state from it
+            // parent window, after this the firstNewPointerIdx in input state will be reset, then
+            // it will cause new move event be thought inconsistent, so we should synthesize the
+            // down event after it reset.
+            synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection,
+                                                           newTargetFlags,
+                                                           traceContext.getTracker());
         }
     } // release lock
 
@@ -7039,7 +7045,7 @@
 void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                                          const sp<WindowInfoHandle>& oldWindowHandle,
                                          const sp<WindowInfoHandle>& newWindowHandle,
-                                         TouchState& state, int32_t deviceId,
+                                         TouchState& state, DeviceId deviceId,
                                          const PointerProperties& pointerProperties,
                                          std::vector<InputTarget>& targets) const {
     std::vector<PointerProperties> pointers{pointerProperties};
@@ -7049,7 +7055,7 @@
             newWindowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
     const sp<WindowInfoHandle> oldWallpaper =
-            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+            oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
             newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
@@ -7075,7 +7081,7 @@
 void InputDispatcher::transferWallpaperTouch(
         ftl::Flags<InputTarget::Flags> oldTargetFlags,
         ftl::Flags<InputTarget::Flags> newTargetFlags, const sp<WindowInfoHandle> fromWindowHandle,
-        const sp<WindowInfoHandle> toWindowHandle, TouchState& state, int32_t deviceId,
+        const sp<WindowInfoHandle> toWindowHandle, TouchState& state, DeviceId deviceId,
         const std::vector<PointerProperties>& pointers,
         const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) {
     const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
@@ -7086,7 +7092,7 @@
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
 
     const sp<WindowInfoHandle> oldWallpaper =
-            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+            oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
             newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 3579a67..3d127c2 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -701,14 +701,14 @@
     void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                             const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
                             const sp<android::gui::WindowInfoHandle>& newWindowHandle,
-                            TouchState& state, int32_t deviceId,
+                            TouchState& state, DeviceId deviceId,
                             const PointerProperties& pointerProperties,
                             std::vector<InputTarget>& targets) const REQUIRES(mLock);
     void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
                                 ftl::Flags<InputTarget::Flags> newTargetFlags,
                                 const sp<android::gui::WindowInfoHandle> fromWindowHandle,
                                 const sp<android::gui::WindowInfoHandle> toWindowHandle,
-                                TouchState& state, int32_t deviceId,
+                                TouchState& state, DeviceId deviceId,
                                 const std::vector<PointerProperties>& pointers,
                                 const std::unique_ptr<trace::EventTrackerInterface>& traceTracker)
             REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 296c334..0c9ad3c 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -211,9 +211,11 @@
     return haveSlipperyForegroundWindow;
 }
 
-sp<WindowInfoHandle> TouchState::getWallpaperWindow() const {
-    for (size_t i = 0; i < windows.size(); i++) {
-        const TouchedWindow& window = windows[i];
+sp<WindowInfoHandle> TouchState::getWallpaperWindow(DeviceId deviceId) const {
+    for (const auto& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.windowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
             return window.windowHandle;
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 9ddb4e2..9d4bb3d 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -66,7 +66,7 @@
 
     sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle(DeviceId deviceId) const;
     bool isSlippery(DeviceId deviceId) const;
-    sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
+    sp<android::gui::WindowInfoHandle> getWallpaperWindow(DeviceId deviceId) const;
     const TouchedWindow& getTouchedWindow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const;
     // Whether any of the windows are currently being touched
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 77e672c..4d8ed99 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -61,9 +61,6 @@
         // The display size or orientation changed.
         DISPLAY_INFO = 1u << 2,
 
-        // The visible touches option changed.
-        SHOW_TOUCHES = 1u << 3,
-
         // The keyboard layouts must be reloaded.
         KEYBOARD_LAYOUTS = 1u << 4,
 
@@ -214,9 +211,6 @@
     // will cover this portion of the display diagonal.
     float pointerGestureZoomSpeedRatio;
 
-    // True to show the location of touches on the touch screen as spots.
-    bool showTouches;
-
     // The latest request to enable or disable Pointer Capture.
     PointerCaptureRequest pointerCaptureRequest;
 
@@ -268,7 +262,6 @@
             pointerGestureSwipeMaxWidthRatio(0.25f),
             pointerGestureMovementSpeedRatio(0.8f),
             pointerGestureZoomSpeedRatio(0.3f),
-            showTouches(false),
             pointerCaptureRequest(),
             touchpadPointerSpeed(0),
             touchpadNaturalScrollingEnabled(true),
@@ -449,10 +442,6 @@
     /* Gets the input reader configuration. */
     virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) = 0;
 
-    /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
-    virtual std::shared_ptr<PointerControllerInterface> obtainPointerController(
-            int32_t deviceId) = 0;
-
     /* Notifies the input reader policy that some input devices have changed
      * and provides information about all current input devices.
      */
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index c44486f..c1467b3 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -61,8 +61,6 @@
      * TODO(b/293587049): Refactor the PointerController class into different controller types.
      */
     enum class ControllerType {
-        // The PointerController that is responsible for drawing all icons.
-        LEGACY,
         // Represents a single mouse pointer.
         MOUSE,
         // Represents multiple touch spots.
@@ -143,6 +141,11 @@
 
     /* Sets the custom pointer icon for mice or styluses. */
     virtual void setCustomPointerIcon(const SpriteIcon& icon) = 0;
+
+    /* Sets the flag to skip screenshot of the pointer indicators on the display matching the
+     * provided displayId.
+     */
+    virtual void setSkipScreenshot(int32_t displayId, bool skip) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 9608210..903c6ed 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -402,10 +402,6 @@
     ALOGI("Reconfiguring input devices, changes=%s", changes.string().c_str());
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
 
-    if (changes.test(Change::DISPLAY_INFO)) {
-        updatePointerDisplayLocked();
-    }
-
     if (changes.test(Change::MUST_REOPEN)) {
         mEventHub->requestReopenDevices();
     } else {
@@ -490,47 +486,6 @@
     }
 }
 
-std::shared_ptr<PointerControllerInterface> InputReader::getPointerControllerLocked(
-        int32_t deviceId) {
-    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
-    if (controller == nullptr) {
-        controller = mPolicy->obtainPointerController(deviceId);
-        mPointerController = controller;
-        updatePointerDisplayLocked();
-    }
-    return controller;
-}
-
-void InputReader::updatePointerDisplayLocked() {
-    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
-    if (controller == nullptr) {
-        return;
-    }
-
-    std::optional<DisplayViewport> viewport =
-            mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
-    if (!viewport) {
-        ALOGW("Can't find the designated viewport with ID %" PRId32 " to update cursor input "
-              "mapper. Fall back to default display",
-              mConfig.defaultPointerDisplayId);
-        viewport = mConfig.getDisplayViewportById(ADISPLAY_ID_DEFAULT);
-    }
-    if (!viewport) {
-        ALOGE("Still can't find a viable viewport to update cursor input mapper. Skip setting it to"
-              " PointerController.");
-        return;
-    }
-
-    controller->setDisplayViewport(*viewport);
-}
-
-void InputReader::fadePointerLocked() {
-    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
-    if (controller != nullptr) {
-        controller->fade(PointerControllerInterface::Transition::GRADUAL);
-    }
-}
-
 void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) {
     if (when < mNextTimeout) {
         mNextTimeout = when;
@@ -1067,17 +1022,6 @@
     return mReader->shouldDropVirtualKeyLocked(now, keyCode, scanCode);
 }
 
-void InputReader::ContextImpl::fadePointer() {
-    // lock is already held by the input loop
-    mReader->fadePointerLocked();
-}
-
-std::shared_ptr<PointerControllerInterface> InputReader::ContextImpl::getPointerController(
-        int32_t deviceId) {
-    // lock is already held by the input loop
-    return mReader->getPointerControllerLocked(deviceId);
-}
-
 void InputReader::ContextImpl::requestTimeoutAtTime(nsecs_t when) {
     // lock is already held by the input loop
     mReader->requestTimeoutAtTimeLocked(when);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 4c78db3..5f882cf 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <PointerControllerInterface.h>
 #include <android-base/thread_annotations.h>
 #include <utils/Condition.h>
 #include <utils/Mutex.h>
@@ -141,9 +140,6 @@
         void disableVirtualKeysUntil(nsecs_t time) REQUIRES(mReader->mLock) override;
         bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode)
                 REQUIRES(mReader->mLock) override;
-        void fadePointer() REQUIRES(mReader->mLock) override;
-        std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId)
-                REQUIRES(mReader->mLock) override;
         void requestTimeoutAtTime(nsecs_t when) REQUIRES(mReader->mLock) override;
         int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override;
         void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices)
@@ -230,13 +226,6 @@
     [[nodiscard]] std::list<NotifyArgs> dispatchExternalStylusStateLocked(const StylusState& state)
             REQUIRES(mLock);
 
-    // The PointerController that is shared among all the input devices that need it.
-    std::weak_ptr<PointerControllerInterface> mPointerController;
-    std::shared_ptr<PointerControllerInterface> getPointerControllerLocked(int32_t deviceId)
-            REQUIRES(mLock);
-    void updatePointerDisplayLocked() REQUIRES(mLock);
-    void fadePointerLocked() REQUIRES(mLock);
-
     int32_t mGeneration GUARDED_BY(mLock);
     int32_t bumpGenerationLocked() REQUIRES(mLock);
 
diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h
index 69b2315..907a49f 100644
--- a/services/inputflinger/reader/include/InputReaderContext.h
+++ b/services/inputflinger/reader/include/InputReaderContext.h
@@ -28,7 +28,6 @@
 class InputListenerInterface;
 class InputMapper;
 class InputReaderPolicyInterface;
-class PointerControllerInterface;
 struct StylusState;
 
 /* Internal interface used by individual input devices to access global input device state
@@ -45,9 +44,6 @@
     virtual void disableVirtualKeysUntil(nsecs_t time) = 0;
     virtual bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) = 0;
 
-    virtual void fadePointer() = 0;
-    virtual std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId) = 0;
-
     virtual void requestTimeoutAtTime(nsecs_t when) = 0;
     virtual int32_t bumpGeneration() = 0;
 
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 738517b..658ceab 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -483,16 +483,11 @@
 void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) {
     InputReaderContext& context = *getContext();
     context.setLastKeyDownTimestamp(downTime);
-    if (context.isPreventingTouchpadTaps()) {
-        // avoid pinging java service unnecessarily, just fade pointer again if it became visible
-        context.fadePointer();
-        return;
-    }
+    // TODO(b/338652288): Move cursor fading logic into PointerChoreographer.
     // Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard
     // shortcuts
     bool shouldHideCursor = mKeyDowns.size() == 1 && !isMetaKey(mKeyDowns[0].keyCode);
     if (shouldHideCursor && context.getPolicy()->isInputMethodConnectionActive()) {
-        context.fadePointer();
         context.setPreventingTouchpadTaps(true);
     }
 }
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index e9118a9..e2dcb41 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -155,11 +155,6 @@
     mConfig.disabledDevices.erase(deviceId);
 }
 
-void FakeInputReaderPolicy::setPointerController(
-        std::shared_ptr<FakePointerController> controller) {
-    mPointerController = std::move(controller);
-}
-
 const InputReaderConfiguration& FakeInputReaderPolicy::getReaderConfiguration() const {
     return mConfig;
 }
@@ -183,10 +178,6 @@
     return mConfig.pointerCaptureRequest;
 }
 
-void FakeInputReaderPolicy::setShowTouches(bool enabled) {
-    mConfig.showTouches = enabled;
-}
-
 void FakeInputReaderPolicy::setDefaultPointerDisplayId(int32_t pointerDisplayId) {
     mConfig.defaultPointerDisplayId = pointerDisplayId;
 }
@@ -228,11 +219,6 @@
     *outConfig = mConfig;
 }
 
-std::shared_ptr<PointerControllerInterface> FakeInputReaderPolicy::obtainPointerController(
-        int32_t /*deviceId*/) {
-    return mPointerController;
-}
-
 void FakeInputReaderPolicy::notifyInputDevicesChanged(
         const std::vector<InputDeviceInfo>& inputDevices) {
     std::scoped_lock lock(mLock);
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 710bb54..88f0ba7 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -26,7 +26,6 @@
 #include <InputDevice.h>
 #include <InputReaderBase.h>
 
-#include "FakePointerController.h"
 #include "input/DisplayViewport.h"
 #include "input/InputDevice.h"
 
@@ -62,14 +61,12 @@
                                       const KeyboardLayoutInfo& layoutInfo);
     void addDisabledDevice(int32_t deviceId);
     void removeDisabledDevice(int32_t deviceId);
-    void setPointerController(std::shared_ptr<FakePointerController> controller);
     const InputReaderConfiguration& getReaderConfiguration() const;
     const std::vector<InputDeviceInfo> getInputDevices() const;
     TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
                                                            ui::Rotation surfaceRotation);
     void setTouchAffineTransformation(const TouchAffineTransformation t);
     PointerCaptureRequest setPointerCapture(const sp<IBinder>& window);
-    void setShowTouches(bool enabled);
     void setDefaultPointerDisplayId(int32_t pointerDisplayId);
     void setPointerGestureEnabled(bool enabled);
     float getPointerGestureMovementSpeedRatio();
@@ -84,8 +81,6 @@
 
 private:
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
-    std::shared_ptr<PointerControllerInterface> obtainPointerController(
-            int32_t /*deviceId*/) override;
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) override;
@@ -97,7 +92,6 @@
     std::condition_variable mDevicesChangedCondition;
 
     InputReaderConfiguration mConfig;
-    std::shared_ptr<FakePointerController> mPointerController;
     std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
     bool mInputDevicesChanged GUARDED_BY(mLock){false};
     std::vector<DisplayViewport> mViewports;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index dc199e2..28d4b67 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -76,6 +76,14 @@
     mCustomIconStyle = icon.style;
 }
 
+void FakePointerController::setSkipScreenshot(int32_t displayId, bool skip) {
+    if (skip) {
+        mDisplaysToSkipScreenshot.insert(displayId);
+    } else {
+        mDisplaysToSkipScreenshot.erase(displayId);
+    }
+};
+
 void FakePointerController::assertViewportSet(int32_t displayId) {
     ASSERT_TRUE(mDisplayId);
     ASSERT_EQ(displayId, mDisplayId);
@@ -117,6 +125,14 @@
     ASSERT_EQ(std::nullopt, mCustomIconStyle);
 }
 
+void FakePointerController::assertIsHiddenOnMirroredDisplays(int32_t displayId, bool isHidden) {
+    if (isHidden) {
+        ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) != mDisplaysToSkipScreenshot.end());
+    } else {
+        ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) == mDisplaysToSkipScreenshot.end());
+    }
+}
+
 bool FakePointerController::isPointerShown() {
     return mIsPointerShown;
 }
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 536b447..b5b982e 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -21,6 +21,7 @@
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <utils/BitSet.h>
+#include <unordered_set>
 
 namespace android {
 
@@ -45,6 +46,7 @@
     void setDisplayViewport(const DisplayViewport& viewport) override;
     void updatePointerIcon(PointerIconStyle iconId) override;
     void setCustomPointerIcon(const SpriteIcon& icon) override;
+    void setSkipScreenshot(int32_t displayId, bool skip) override;
     void fade(Transition) override;
 
     void assertViewportSet(int32_t displayId);
@@ -55,6 +57,7 @@
     void assertPointerIconNotSet();
     void assertCustomPointerIconSet(PointerIconStyle iconId);
     void assertCustomPointerIconNotSet();
+    void assertIsHiddenOnMirroredDisplays(int32_t displayId, bool isHidden);
     bool isPointerShown();
 
 private:
@@ -77,6 +80,7 @@
     std::optional<PointerIconStyle> mCustomIconStyle;
 
     std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
+    std::unordered_set<int32_t> mDisplaysToSkipScreenshot;
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index bc173b1..09b358a 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -117,7 +117,7 @@
 // An arbitrary pid of the gesture monitor window
 static constexpr gui::Pid MONITOR_PID{2001};
 
-static constexpr int expectedWallpaperFlags =
+static constexpr int EXPECTED_WALLPAPER_FLAGS =
         AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
 using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID;
@@ -827,7 +827,7 @@
 
     // Both foreground window and its wallpaper should receive the touch down
     foregroundWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
@@ -837,13 +837,13 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     foregroundWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
-    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Now the foreground window goes away, but the wallpaper stays
     mDispatcher->onWindowInfosChanged({{*wallpaperWindow->getInfo()}, {}, 0, 0});
     foregroundWindow->consumeMotionCancel();
     // Since the "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
-    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 }
 
 /**
@@ -908,7 +908,7 @@
 
     // Both foreground window and its wallpaper should receive the touch down
     foregroundWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
@@ -916,7 +916,7 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     foregroundWindow->consumeMotionMove();
-    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Wallpaper closes its channel, but the window remains.
     wallpaperWindow->destroyReceiver();
@@ -928,6 +928,301 @@
     foregroundWindow->consumeMotionCancel();
 }
 
+/**
+ * Two windows: left and right, and a separate wallpaper window underneath each. Device A sends a
+ * down event to the left window. Device B sends a down event to the right window. Next, the right
+ * window disappears. Both the right window and its wallpaper window should receive cancel event.
+ * The left window and its wallpaper window should not receive any events.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceDisappearingWindowWithWallpaperWindows) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left foreground window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftForegroundWindow->setFrame(Rect(0, 0, 100, 100));
+    leftForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left wallpaper window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> rightForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right foreground window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightForegroundWindow->setFrame(Rect(100, 0, 200, 100));
+    rightForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> rightWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right wallpaper window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightWallpaperWindow->setFrame(Rect(100, 0, 200, 100));
+    rightWallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                  WithDeviceId(deviceA),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                   WithDeviceId(deviceB),
+                                                   WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+
+    // Now right foreground window disappears, but right wallpaper window remains.
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    // Left foreground window and left wallpaper window still exist, and should not receive any
+    // events.
+    leftForegroundWindow->assertNoEvents();
+    leftWallpaperWindow->assertNoEvents();
+    // Since right foreground window disappeared, right wallpaper window and right foreground window
+    // should receive cancel events.
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED)));
+}
+
+/**
+ * Three windows arranged horizontally and without any overlap. Every window has a
+ * wallpaper window underneath. The middle window also has SLIPPERY flag.
+ * Device A sends a down event to the left window. Device B sends a down event to the middle window.
+ * Next, device B sends move event to the right window. Touch for device B should slip from the
+ * middle window to the right window. Also, the right wallpaper window should receive a down event.
+ * The middle window and its wallpaper window should receive a cancel event. The left window should
+ * not receive any events. If device B continues to report events, the right window and its
+ * wallpaper window should receive remaining events.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSlipperyTouchWithWallpaperWindow) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left foreground window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftForegroundWindow->setFrame(Rect(0, 0, 100, 100));
+    leftForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left wallpaper window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> middleForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle foreground window",
+                                       ADISPLAY_ID_DEFAULT);
+    middleForegroundWindow->setFrame(Rect(100, 0, 200, 100));
+    middleForegroundWindow->setDupTouchToWallpaper(true);
+    middleForegroundWindow->setSlippery(true);
+    sp<FakeWindowHandle> middleWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle wallpaper window",
+                                       ADISPLAY_ID_DEFAULT);
+    middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100));
+    middleWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> rightForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right foreground window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightForegroundWindow->setFrame(Rect(200, 0, 300, 100));
+    rightForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> rightWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right wallpaper window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100));
+    rightWallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *middleForegroundWindow->getInfo(), *middleWallpaperWindow->getInfo(),
+              *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+    // Device A sends a DOWN event to the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                  WithDeviceId(deviceA),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Device B sends a DOWN event to the middle window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Move the events of device B to the top of the right window.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED)));
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                   WithDeviceId(deviceB),
+                                                   WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Make sure the window on the right can receive the remaining events.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(251).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftForegroundWindow->assertNoEvents();
+    leftWallpaperWindow->assertNoEvents();
+    middleForegroundWindow->assertNoEvents();
+    middleWallpaperWindow->assertNoEvents();
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE),
+                                                   WithDeviceId(deviceB),
+                                                   WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+}
+
+/**
+ * Similar to the test above, we have three windows, they are arranged horizontally and without any
+ * overlap, and every window has a wallpaper window. The middle window is a simple window, without
+ * any special flags. Device A reports a down event that lands in left window. Device B sends a down
+ * event to the middle window and then touch is transferred from the middle window to the right
+ * window. The right window and its wallpaper window should receive a down event. The middle window
+ * and its wallpaper window should receive a cancel event. The left window should not receive any
+ * events. Subsequent events reported by device B should go to the right window and its wallpaper.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceTouchTransferWithWallpaperWindows) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left foreground window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftForegroundWindow->setFrame(Rect(0, 0, 100, 100));
+    leftForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left wallpaper window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> middleForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle foreground window",
+                                       ADISPLAY_ID_DEFAULT);
+    middleForegroundWindow->setFrame(Rect(100, 0, 200, 100));
+    middleForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> middleWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle wallpaper window",
+                                       ADISPLAY_ID_DEFAULT);
+    middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100));
+    middleWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> rightForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right foreground window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightForegroundWindow->setFrame(Rect(200, 0, 300, 100));
+    rightForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> rightWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right wallpaper window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100));
+    rightWallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *middleForegroundWindow->getInfo(), *middleWallpaperWindow->getInfo(),
+              *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+    // Device A touch down on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                  WithDeviceId(deviceA),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Device B touch down on the middle window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+
+    // Transfer touch from the middle window to the right window.
+    ASSERT_TRUE(mDispatcher->transferTouchGesture(middleForegroundWindow->getToken(),
+                                                  rightForegroundWindow->getToken()));
+
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED)));
+    rightForegroundWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+    rightWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+
+    // Make sure the right window can receive the remaining events.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(251).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftForegroundWindow->assertNoEvents();
+    leftWallpaperWindow->assertNoEvents();
+    middleForegroundWindow->assertNoEvents();
+    middleWallpaperWindow->assertNoEvents();
+    rightForegroundWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+    rightWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+}
+
 class ShouldSplitTouchFixture : public InputDispatcherTest,
                                 public ::testing::WithParamInterface<bool> {};
 INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture,
@@ -959,7 +1254,7 @@
 
     // Both top window and its wallpaper should receive the touch down
     foregroundWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Second finger down on the top window
     const MotionEvent secondFingerDownEvent =
@@ -975,7 +1270,7 @@
 
     foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1);
     wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
-                                              expectedWallpaperFlags);
+                                              EXPECTED_WALLPAPER_FLAGS);
 
     const MotionEvent secondFingerUpEvent =
             MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
@@ -989,7 +1284,7 @@
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     foregroundWindow->consumeMotionPointerUp(0);
-    wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
@@ -1004,7 +1299,7 @@
                                 INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 }
 
 /**
@@ -1046,7 +1341,7 @@
 
     // Both foreground window and its wallpaper should receive the touch down
     leftWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Second finger down on the right window
     const MotionEvent secondFingerDownEvent =
@@ -1064,14 +1359,14 @@
     // Since the touch is split, right window gets ACTION_DOWN
     rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
-                                              expectedWallpaperFlags);
+                                              EXPECTED_WALLPAPER_FLAGS);
 
     // Now, leftWindow, which received the first finger, disappears.
     mDispatcher->onWindowInfosChanged(
             {{*rightWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
     leftWindow->consumeMotionCancel();
     // Since a "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
-    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // The pointer that's still down on the right window moves, and goes to the right window only.
     // As far as the dispatcher's concerned though, both pointers are still present.
@@ -1126,7 +1421,7 @@
 
     // Both foreground window and its wallpaper should receive the touch down
     leftWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Move to right window, the left window should receive cancel.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1136,7 +1431,7 @@
 
     leftWindow->consumeMotionCancel();
     rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 }
 
 /**
@@ -5763,7 +6058,7 @@
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
-    wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
     // Dispatcher reports pointer down outside focus for the wallpaper
     mFakePolicy->assertOnPointerDownEquals(wallpaper->getToken());
 
@@ -5774,7 +6069,7 @@
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
     secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
-    wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
     // There should not be any changes to the focused window when transferring touch
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertOnPointerDownWasNotCalled());
 
@@ -5935,7 +6230,7 @@
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
-    wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
     wallpaper2->assertNoEvents();
 
     // Transfer touch focus to the second window
@@ -5946,9 +6241,9 @@
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
     secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
-    wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS);
     wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT,
-                                  expectedWallpaperFlags | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+                                  EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
@@ -5958,7 +6253,7 @@
     secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     wallpaper1->assertNoEvents();
     wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT,
-                                expectedWallpaperFlags | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+                                EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 // For the cases of single pointer touch and two pointers non-split touch, the api's
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index e1b46fa..92489ae 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -46,7 +46,6 @@
 #include <thread>
 #include "FakeEventHub.h"
 #include "FakeInputReaderPolicy.h"
-#include "FakePointerController.h"
 #include "InputMapperTest.h"
 #include "InstrumentedInputReader.h"
 #include "TestConstants.h"
@@ -1349,8 +1348,6 @@
     sp<FakeInputReaderPolicy> mFakePolicy;
     std::unique_ptr<InputReaderInterface> mReader;
 
-    std::shared_ptr<FakePointerController> mFakePointerController;
-
     constexpr static auto EVENT_HAPPENED_TIMEOUT = 2000ms;
     constexpr static auto EVENT_DID_NOT_HAPPEN_TIMEOUT = 30ms;
 
@@ -1359,8 +1356,6 @@
         GTEST_SKIP();
 #endif
         mFakePolicy = sp<FakeInputReaderPolicy>::make();
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
 
         setupInputReader();
     }
@@ -1654,8 +1649,6 @@
         } else {
             mFakePolicy->addInputUniqueIdAssociation(INPUT_PORT, UNIQUE_ID);
         }
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
 
         InputReaderIntegrationTest::setupInputReader();
 
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index db89168..6389cdc 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -28,7 +28,6 @@
 #include <EventHub.h>
 #include <InputReaderBase.h>
 #include <NotifyArgs.h>
-#include <PointerControllerInterface.h>
 #include <StylusState.h>
 #include <VibrationElement.h>
 #include <android-base/logging.h>
@@ -54,10 +53,6 @@
     MOCK_METHOD(bool, shouldDropVirtualKey, (nsecs_t now, int32_t keyCode, int32_t scanCode),
                 (override));
 
-    MOCK_METHOD(void, fadePointer, (), (override));
-    MOCK_METHOD(std::shared_ptr<PointerControllerInterface>, getPointerController,
-                (int32_t deviceId), (override));
-
     MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override));
     int32_t bumpGeneration() override { return ++mGeneration; }
 
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index b44529b..031b77d 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -72,8 +72,6 @@
     }
 
     void testPointerVisibilityForKeys(const std::vector<int32_t>& keyCodes, bool expectVisible) {
-        EXPECT_CALL(mMockInputReaderContext, fadePointer)
-                .Times(expectVisible ? 0 : keyCodes.size());
         for (int32_t keyCode : keyCodes) {
             process(EV_KEY, keyCode, 1);
             process(EV_SYN, SYN_REPORT, 0);
@@ -84,7 +82,6 @@
 
     void testTouchpadTapStateForKeys(const std::vector<int32_t>& keyCodes,
                                      const bool expectPrevent) {
-        EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).Times(keyCodes.size());
         if (expectPrevent) {
             EXPECT_CALL(mMockInputReaderContext, setPreventingTouchpadTaps(true))
                     .Times(keyCodes.size());
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 11c6b7e..d81f8a0 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -15,7 +15,8 @@
  */
 
 #include "../PointerChoreographer.h"
-
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gtest/gtest.h>
 #include <deque>
 #include <vector>
@@ -27,6 +28,8 @@
 
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+
 using ControllerType = PointerControllerInterface::ControllerType;
 using testing::AllOf;
 
@@ -1587,6 +1590,76 @@
     firstMousePc->assertPointerIconNotSet();
 }
 
+TEST_F_WITH_FLAGS(PointerChoreographerTest, HidesTouchSpotsOnMirroredDisplaysForSecureWindow,
+                  REQUIRES_FLAGS_ENABLED(
+                          ACONFIG_FLAG(input_flags, hide_pointer_indicators_for_secure_windows))) {
+    // Add a touch device and enable show touches.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+
+    // Emit touch events to create PointerController
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // By default touch indicators should not be hidden
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+
+    // adding secure window on display should set flag to hide pointer indicators on corresponding
+    // mirrored display
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    mChoreographer.onWindowInfosChanged({windowInfo});
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/true);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+
+    // removing the secure window should reset the state
+    windowInfo.inputConfig.clear(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY);
+    mChoreographer.onWindowInfosChanged({windowInfo});
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+}
+
+TEST_F_WITH_FLAGS(PointerChoreographerTest,
+                  DoesNotHidesTouchSpotsOnMirroredDisplaysForInvisibleWindow,
+                  REQUIRES_FLAGS_ENABLED(
+                          ACONFIG_FLAG(input_flags, hide_pointer_indicators_for_secure_windows))) {
+    // Add a touch device and enable show touches.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+
+    // Emit touch events to create PointerController
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // By default touch indicators should not be hidden
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+
+    // adding secure but hidden window on display should still not set flag to hide pointer
+    // indicators
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_VISIBLE;
+    mChoreographer.onWindowInfosChanged({windowInfo});
+    pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false);
+    pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false);
+}
+
 TEST_P(StylusTestFixture, SetsPointerIconForStylus) {
     const auto& [name, source, controllerType] = GetParam();
 
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 7898126..e020ca9 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -258,56 +258,16 @@
     void sysfsNodeChanged(const std::string& sysfsNodePath) override {}
 };
 
-class FuzzPointerController : public PointerControllerInterface {
-    std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
-
-public:
-    FuzzPointerController(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
-    ~FuzzPointerController() {}
-    std::optional<FloatRect> getBounds() const override {
-        if (mFdp->ConsumeBool()) {
-            return {};
-        } else {
-            return FloatRect{mFdp->ConsumeFloatingPoint<float>(),
-                             mFdp->ConsumeFloatingPoint<float>(),
-                             mFdp->ConsumeFloatingPoint<float>(),
-                             mFdp->ConsumeFloatingPoint<float>()};
-        }
-    }
-    void move(float deltaX, float deltaY) override {}
-    void setPosition(float x, float y) override {}
-    FloatPoint getPosition() const override {
-        return {mFdp->ConsumeFloatingPoint<float>(), mFdp->ConsumeFloatingPoint<float>()};
-    }
-    void fade(Transition transition) override {}
-    void unfade(Transition transition) override {}
-    void setPresentation(Presentation presentation) override {}
-    void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                  BitSet32 spotIdBits, int32_t displayId) override {}
-    void clearSpots() override {}
-    int32_t getDisplayId() const override { return mFdp->ConsumeIntegral<int32_t>(); }
-    void setDisplayViewport(const DisplayViewport& displayViewport) override {}
-    void updatePointerIcon(PointerIconStyle iconId) override {}
-    void setCustomPointerIcon(const SpriteIcon& icon) override {}
-    std::string dump() override { return ""; }
-};
-
 class FuzzInputReaderPolicy : public InputReaderPolicyInterface {
     TouchAffineTransformation mTransform;
-    std::shared_ptr<FuzzPointerController> mPointerController;
     std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
 
 protected:
     ~FuzzInputReaderPolicy() {}
 
 public:
-    FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {
-        mPointerController = std::make_shared<FuzzPointerController>(mFdp);
-    }
+    FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override {}
-    std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override {
-        return mPointerController;
-    }
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {}
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
@@ -359,10 +319,6 @@
     bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) override {
         return mFdp->ConsumeBool();
     }
-    void fadePointer() override {}
-    std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId) override {
-        return mPolicy->obtainPointerController(0);
-    }
     void requestTimeoutAtTime(nsecs_t when) override {}
     int32_t bumpGeneration() override { return mFdp->ConsumeIntegral<int32_t>(); }
     void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) override {}
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ccb3aa7..ccaa05f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -346,7 +346,9 @@
     // Used to skip event dispatch before EventThread creation during boot.
     // TODO: b/241285191 - Reorder Scheduler initialization to avoid this.
     bool hasEventThreads() const {
-        return CC_LIKELY(mRenderEventThread && mLastCompositeEventThread);
+        return CC_LIKELY(
+                mRenderEventThread &&
+                (FlagManager::getInstance().deprecate_vsync_sf() || mLastCompositeEventThread));
     }
 
     EventThread& eventThreadFor(Cycle cycle) const {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 0369549..0d2e514 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -425,7 +425,8 @@
         mInternalDisplayDensity(
                 getDensityFromProperty("ro.sf.lcd_density", !mEmulatedDisplayDensity)),
         mPowerAdvisor(std::make_unique<Hwc2::impl::PowerAdvisor>(*this)),
-        mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make()) {
+        mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make()),
+        mSkipPowerOnForQuiescent(base::GetBoolProperty("ro.boot.quiescent"s, false)) {
     ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str());
 }
 
@@ -3883,7 +3884,9 @@
 
         if (currentState.physical) {
             const auto display = getDisplayDeviceLocked(displayToken);
-            setPowerModeInternal(display, hal::PowerMode::ON);
+            if (!mSkipPowerOnForQuiescent) {
+                setPowerModeInternal(display, hal::PowerMode::ON);
+            }
 
             // TODO(b/175678251) Call a listener instead.
             if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
@@ -6102,8 +6105,11 @@
         // Power on all displays. The primary display is first, so becomes the active display. Also,
         // the DisplayCapability set of a display is populated on its first powering on. Do this now
         // before responding to any Binder query from DisplayManager about display capabilities.
-        for (const auto& [id, display] : mPhysicalDisplays) {
-            setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON);
+        // Additionally, do not turn on displays if the boot should be quiescent.
+        if (!mSkipPowerOnForQuiescent) {
+            for (const auto& [id, display] : mPhysicalDisplays) {
+                setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON);
+            }
         }
     }
 }
@@ -6262,6 +6268,7 @@
 void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
     auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
                                                kMainThreadContext) {
+        mSkipPowerOnForQuiescent = false;
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
             ALOGE("Attempt to set power mode %d for invalid display token %p", mode,
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index af8f315..4cb5aa3 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1494,6 +1494,8 @@
     bool mPowerHintSessionEnabled;
 
     bool mLayerLifecycleManagerEnabled = false;
+    // Whether a display should be turned on when initialized
+    bool mSkipPowerOnForQuiescent;
 
     frontend::LayerLifecycleManager mLayerLifecycleManager GUARDED_BY(kMainThreadContext);
     frontend::LayerHierarchyBuilder mLayerHierarchyBuilder GUARDED_BY(kMainThreadContext);
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index d3b6e17..a0c1372 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -448,6 +448,7 @@
     void commitTransactionsLocked(uint32_t transactionFlags) {
         Mutex::Autolock lock(mFlinger->mStateLock);
         ftl::FakeGuard guard(kMainThreadContext);
+        mFlinger->processDisplayChangesLocked();
         mFlinger->commitTransactionsLocked(transactionFlags);
     }