Add Window Infos state to dumpsys

Update SurfaceFlinger and InputDispatcher dumpsys output to include
the vsync id and timestamp of Window Infos changes.

Bug: 279792237
Test: presubmits
Change-Id: I17fba2d09972467cfdd452e5041666ffbeabddc9
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 0a63c15..33bb343 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -75,6 +75,7 @@
         "android/gui/IWindowInfosListener.aidl",
         "android/gui/IWindowInfosReportedListener.aidl",
         "android/gui/WindowInfo.aidl",
+        "android/gui/WindowInfosUpdate.aidl",
     ],
 }
 
@@ -90,9 +91,11 @@
         "android/gui/InputApplicationInfo.aidl",
         "android/gui/IWindowInfosListener.aidl",
         "android/gui/IWindowInfosReportedListener.aidl",
+        "android/gui/WindowInfosUpdate.aidl",
         "android/gui/WindowInfo.aidl",
         "DisplayInfo.cpp",
         "WindowInfo.cpp",
+        "WindowInfosUpdate.cpp",
     ],
 
     shared_libs: [
diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp
index 2b34a0f..76e7b6e 100644
--- a/libs/gui/WindowInfosListenerReporter.cpp
+++ b/libs/gui/WindowInfosListenerReporter.cpp
@@ -17,6 +17,7 @@
 #include <android/gui/ISurfaceComposer.h>
 #include <gui/AidlStatusUtil.h>
 #include <gui/WindowInfosListenerReporter.h>
+#include "gui/WindowInfosUpdate.h"
 
 namespace android {
 
@@ -84,7 +85,7 @@
 }
 
 binder::Status WindowInfosListenerReporter::onWindowInfosChanged(
-        const std::vector<WindowInfo>& windowInfos, const std::vector<DisplayInfo>& displayInfos,
+        const gui::WindowInfosUpdate& update,
         const sp<IWindowInfosReportedListener>& windowInfosReportedListener) {
     std::unordered_set<sp<WindowInfosListener>, gui::SpHash<WindowInfosListener>>
             windowInfosListeners;
@@ -95,12 +96,12 @@
             windowInfosListeners.insert(listener);
         }
 
-        mLastWindowInfos = windowInfos;
-        mLastDisplayInfos = displayInfos;
+        mLastWindowInfos = update.windowInfos;
+        mLastDisplayInfos = update.displayInfos;
     }
 
     for (auto listener : windowInfosListeners) {
-        listener->onWindowInfosChanged(windowInfos, displayInfos);
+        listener->onWindowInfosChanged(update);
     }
 
     if (windowInfosReportedListener) {
diff --git a/libs/gui/WindowInfosUpdate.cpp b/libs/gui/WindowInfosUpdate.cpp
new file mode 100644
index 0000000..38ae5ef
--- /dev/null
+++ b/libs/gui/WindowInfosUpdate.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gui/WindowInfosUpdate.h>
+#include <private/gui/ParcelUtils.h>
+
+namespace android::gui {
+
+status_t WindowInfosUpdate::readFromParcel(const android::Parcel* parcel) {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    uint32_t size;
+
+    SAFE_PARCEL(parcel->readUint32, &size);
+    windowInfos.reserve(size);
+    for (uint32_t i = 0; i < size; i++) {
+        windowInfos.push_back({});
+        SAFE_PARCEL(windowInfos.back().readFromParcel, parcel);
+    }
+
+    SAFE_PARCEL(parcel->readUint32, &size);
+    displayInfos.reserve(size);
+    for (uint32_t i = 0; i < size; i++) {
+        displayInfos.push_back({});
+        SAFE_PARCEL(displayInfos.back().readFromParcel, parcel);
+    }
+
+    SAFE_PARCEL(parcel->readInt64, &vsyncId);
+    SAFE_PARCEL(parcel->readInt64, &timestamp);
+
+    return OK;
+}
+
+status_t WindowInfosUpdate::writeToParcel(android::Parcel* parcel) const {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(windowInfos.size()));
+    for (auto& windowInfo : windowInfos) {
+        SAFE_PARCEL(windowInfo.writeToParcel, parcel);
+    }
+
+    SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(displayInfos.size()));
+    for (auto& displayInfo : displayInfos) {
+        SAFE_PARCEL(displayInfo.writeToParcel, parcel);
+    }
+
+    SAFE_PARCEL(parcel->writeInt64, vsyncId);
+    SAFE_PARCEL(parcel->writeInt64, timestamp);
+
+    return OK;
+}
+
+} // namespace android::gui
diff --git a/libs/gui/android/gui/IWindowInfosListener.aidl b/libs/gui/android/gui/IWindowInfosListener.aidl
index a5b2762..400229d 100644
--- a/libs/gui/android/gui/IWindowInfosListener.aidl
+++ b/libs/gui/android/gui/IWindowInfosListener.aidl
@@ -16,12 +16,11 @@
 
 package android.gui;
 
-import android.gui.DisplayInfo;
 import android.gui.IWindowInfosReportedListener;
-import android.gui.WindowInfo;
+import android.gui.WindowInfosUpdate;
 
 /** @hide */
-oneway interface IWindowInfosListener
-{
-    void onWindowInfosChanged(in WindowInfo[] windowInfos, in DisplayInfo[] displayInfos, in @nullable IWindowInfosReportedListener windowInfosReportedListener);
+oneway interface IWindowInfosListener {
+    void onWindowInfosChanged(
+        in WindowInfosUpdate update, in @nullable IWindowInfosReportedListener windowInfosReportedListener);
 }
diff --git a/libs/gui/android/gui/WindowInfosUpdate.aidl b/libs/gui/android/gui/WindowInfosUpdate.aidl
new file mode 100644
index 0000000..0c6109d
--- /dev/null
+++ b/libs/gui/android/gui/WindowInfosUpdate.aidl
@@ -0,0 +1,22 @@
+/*
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.gui;
+
+import android.gui.DisplayInfo;
+import android.gui.WindowInfo;
+
+parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h";
diff --git a/libs/gui/include/gui/WindowInfosListener.h b/libs/gui/include/gui/WindowInfosListener.h
index a18a498..02c8eb5 100644
--- a/libs/gui/include/gui/WindowInfosListener.h
+++ b/libs/gui/include/gui/WindowInfosListener.h
@@ -16,15 +16,13 @@
 
 #pragma once
 
-#include <gui/DisplayInfo.h>
-#include <gui/WindowInfo.h>
+#include <gui/WindowInfosUpdate.h>
 #include <utils/RefBase.h>
 
 namespace android::gui {
 
 class WindowInfosListener : public virtual RefBase {
 public:
-    virtual void onWindowInfosChanged(const std::vector<WindowInfo>&,
-                                      const std::vector<DisplayInfo>&) = 0;
+    virtual void onWindowInfosChanged(const WindowInfosUpdate& update) = 0;
 };
-} // namespace android::gui
\ No newline at end of file
+} // namespace android::gui
diff --git a/libs/gui/include/gui/WindowInfosListenerReporter.h b/libs/gui/include/gui/WindowInfosListenerReporter.h
index 2754442..38cb108 100644
--- a/libs/gui/include/gui/WindowInfosListenerReporter.h
+++ b/libs/gui/include/gui/WindowInfosListenerReporter.h
@@ -22,6 +22,7 @@
 #include <binder/IBinder.h>
 #include <gui/SpHash.h>
 #include <gui/WindowInfosListener.h>
+#include <gui/WindowInfosUpdate.h>
 #include <unordered_set>
 
 namespace android {
@@ -29,8 +30,7 @@
 class WindowInfosListenerReporter : public gui::BnWindowInfosListener {
 public:
     static sp<WindowInfosListenerReporter> getInstance();
-    binder::Status onWindowInfosChanged(const std::vector<gui::WindowInfo>&,
-                                        const std::vector<gui::DisplayInfo>&,
+    binder::Status onWindowInfosChanged(const gui::WindowInfosUpdate& update,
                                         const sp<gui::IWindowInfosReportedListener>&) override;
     status_t addWindowInfosListener(
             const sp<gui::WindowInfosListener>& windowInfosListener,
diff --git a/libs/gui/include/gui/WindowInfosUpdate.h b/libs/gui/include/gui/WindowInfosUpdate.h
new file mode 100644
index 0000000..2ca59fb
--- /dev/null
+++ b/libs/gui/include/gui/WindowInfosUpdate.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <binder/Parcelable.h>
+#include <gui/DisplayInfo.h>
+#include <gui/WindowInfo.h>
+
+namespace android::gui {
+
+struct WindowInfosUpdate : public Parcelable {
+    WindowInfosUpdate() {}
+
+    WindowInfosUpdate(std::vector<WindowInfo> windowInfos, std::vector<DisplayInfo> displayInfos,
+                      int64_t vsyncId, int64_t timestamp)
+          : windowInfos(std::move(windowInfos)),
+            displayInfos(std::move(displayInfos)),
+            vsyncId(vsyncId),
+            timestamp(timestamp) {}
+
+    std::vector<WindowInfo> windowInfos;
+    std::vector<DisplayInfo> displayInfos;
+    int64_t vsyncId;
+    int64_t timestamp;
+
+    status_t writeToParcel(android::Parcel*) const override;
+    status_t readFromParcel(const android::Parcel*) override;
+};
+
+} // namespace android::gui
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index f852001..becf70a 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -343,8 +343,10 @@
     std::vector<gui::DisplayInfo> displayInfos{info};
 
     for (auto _ : state) {
-        dispatcher.onWindowInfosChanged(windowInfos, displayInfos);
-        dispatcher.onWindowInfosChanged(/*windowInfos=*/{}, /*displayInfos=*/{});
+        dispatcher.onWindowInfosChanged(
+                {windowInfos, displayInfos, /*vsyncId=*/0, /*timestamp=*/0});
+        dispatcher.onWindowInfosChanged(
+                {/*windowInfos=*/{}, /*displayInfos=*/{}, /*vsyncId=*/{}, /*timestamp=*/0});
     }
     dispatcher.stop();
 }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 8c08ef2..bf52502 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -5572,6 +5572,14 @@
     } else {
         dump += INDENT "Displays: <none>\n";
     }
+    dump += INDENT "Window Infos:\n";
+    dump += StringPrintf(INDENT2 "vsync id: %" PRId64 "\n", mWindowInfosVsyncId);
+    dump += StringPrintf(INDENT2 "timestamp (ns): %" PRId64 "\n", mWindowInfosTimestamp);
+    dump += "\n";
+    dump += StringPrintf(INDENT2 "max update delay (ns): %" PRId64 "\n", mMaxWindowInfosDelay);
+    dump += StringPrintf(INDENT2 "max update delay vsync id: %" PRId64 "\n",
+                         mMaxWindowInfosDelayVsyncId);
+    dump += "\n";
 
     if (!mGlobalMonitorsByDisplay.empty()) {
         for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) {
@@ -6580,12 +6588,11 @@
     mLooper->wake();
 }
 
-void InputDispatcher::onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
-                                           const std::vector<DisplayInfo>& displayInfos) {
+void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) {
     // The listener sends the windows as a flattened array. Separate the windows by display for
     // more convenient parsing.
     std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
-    for (const auto& info : windowInfos) {
+    for (const auto& info : update.windowInfos) {
         handlesPerDisplay.emplace(info.displayId, std::vector<sp<WindowInfoHandle>>());
         handlesPerDisplay[info.displayId].push_back(sp<WindowInfoHandle>::make(info));
     }
@@ -6600,13 +6607,22 @@
         }
 
         mDisplayInfos.clear();
-        for (const auto& displayInfo : displayInfos) {
+        for (const auto& displayInfo : update.displayInfos) {
             mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
         }
 
         for (const auto& [displayId, handles] : handlesPerDisplay) {
             setInputWindowsLocked(handles, displayId);
         }
+
+        mWindowInfosVsyncId = update.vsyncId;
+        mWindowInfosTimestamp = update.timestamp;
+
+        int64_t delay = systemTime() - update.timestamp;
+        if (delay > mMaxWindowInfosDelay) {
+            mMaxWindowInfosDelay = delay;
+            mMaxWindowInfosDelayVsyncId = update.vsyncId;
+        }
     }
     // Wake up poll loop since it may need to make new input dispatching choices.
     mLooper->wake();
@@ -6629,9 +6645,8 @@
 }
 
 void InputDispatcher::DispatcherWindowListener::onWindowInfosChanged(
-        const std::vector<gui::WindowInfo>& windowInfos,
-        const std::vector<DisplayInfo>& displayInfos) {
-    mDispatcher.onWindowInfosChanged(windowInfos, displayInfos);
+        const gui::WindowInfosUpdate& update) {
+    mDispatcher.onWindowInfosChanged(update);
 }
 
 void InputDispatcher::cancelCurrentTouch() {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index dd7f7fe..791dd63 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -36,7 +36,7 @@
 
 #include <attestation/HmacKeyManager.h>
 #include <gui/InputApplication.h>
-#include <gui/WindowInfo.h>
+#include <gui/WindowInfosUpdate.h>
 #include <input/Input.h>
 #include <input/InputTransport.h>
 #include <limits.h>
@@ -144,8 +144,7 @@
     void displayRemoved(int32_t displayId) override;
 
     // Public because it's also used by tests to simulate the WindowInfosListener callback
-    void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>& windowInfos,
-                              const std::vector<android::gui::DisplayInfo>& displayInfos);
+    void onWindowInfosChanged(const gui::WindowInfosUpdate&);
 
     void cancelCurrentTouch() override;
 
@@ -205,6 +204,11 @@
 
     const IdGenerator mIdGenerator;
 
+    int64_t mWindowInfosVsyncId GUARDED_BY(mLock);
+    int64_t mWindowInfosTimestamp GUARDED_BY(mLock);
+    int64_t mMaxWindowInfosDelay GUARDED_BY(mLock) = -1;
+    int64_t mMaxWindowInfosDelayVsyncId GUARDED_BY(mLock) = -1;
+
     // With each iteration, InputDispatcher nominally processes one queued event,
     // a timeout, or a response from an input consumer.
     // This method should only be called on the input dispatcher's own thread.
@@ -356,9 +360,7 @@
     class DispatcherWindowListener : public gui::WindowInfosListener {
     public:
         explicit DispatcherWindowListener(InputDispatcher& dispatcher) : mDispatcher(dispatcher){};
-        void onWindowInfosChanged(
-                const std::vector<android::gui::WindowInfo>& windowInfos,
-                const std::vector<android::gui::DisplayInfo>& displayInfos) override;
+        void onWindowInfosChanged(const gui::WindowInfosUpdate&) override;
 
     private:
         InputDispatcher& mDispatcher;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index b3c5095..c0bc68c 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -3771,7 +3771,7 @@
                                                              "Fake Window", ADISPLAY_ID_DEFAULT);
     window->setFocusable(true);
 
-    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
 
     window->consumeFocusEvent(true);
@@ -3785,7 +3785,7 @@
     window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
 
     // All windows are removed from the display. Ensure that we can no longer dispatch to it.
-    mDispatcher->onWindowInfosChanged({}, {});
+    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
 
     window->consumeFocusEvent(false);
 
@@ -3801,7 +3801,7 @@
     // Ensure window is non-split and have some transform.
     window->setPreventSplitting(true);
     window->setWindowOffset(20, 40);
-    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
@@ -3848,12 +3848,12 @@
         info.displayId = displayId;
         info.transform = transform;
         mDisplayInfos.push_back(std::move(info));
-        mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos);
+        mDispatcher->onWindowInfosChanged({mWindowInfos, mDisplayInfos, 0, 0});
     }
 
     void addWindow(const sp<WindowInfoHandle>& windowHandle) {
         mWindowInfos.push_back(*windowHandle->getInfo());
-        mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos);
+        mDispatcher->onWindowInfosChanged({mWindowInfos, mDisplayInfos, 0, 0});
     }
 
     void removeAllWindowsAndDisplays() {
@@ -4952,7 +4952,7 @@
     displayInfo.displayId = ADISPLAY_ID_DEFAULT;
     displayInfo.transform = transform;
 
-    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {displayInfo});
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0});
 
     const NotifyMotionArgs motionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
@@ -5722,7 +5722,7 @@
     displayInfos[1].displayId = SECOND_DISPLAY_ID;
     displayInfos[1].transform = secondDisplayTransform;
 
-    mDispatcher->onWindowInfosChanged({}, displayInfos);
+    mDispatcher->onWindowInfosChanged({{}, displayInfos, 0, 0});
 
     // Enable InputFilter
     mDispatcher->setInputFilterEnabled(true);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d406aff..0e3c47b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2533,7 +2533,7 @@
     }
 
     updateCursorAsync();
-    updateInputFlinger();
+    updateInputFlinger(vsyncId);
 
     if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
         // This will block and tracing should only be enabled for debugging.
@@ -3718,7 +3718,7 @@
     doCommitTransactions();
 }
 
-void SurfaceFlinger::updateInputFlinger() {
+void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId) {
     if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) {
         return;
     }
@@ -3730,6 +3730,8 @@
     if (mUpdateInputInfo) {
         mUpdateInputInfo = false;
         updateWindowInfo = true;
+        mLastInputFlingerUpdateVsyncId = vsyncId;
+        mLastInputFlingerUpdateTimestamp = systemTime();
         buildWindowInfos(windowInfos, displayInfos);
     }
 
@@ -3759,7 +3761,9 @@
                                          std::move(
                                                  inputWindowCommands.windowInfosReportedListeners),
                                          /* forceImmediateCall= */ visibleWindowsChanged ||
-                                                 !inputWindowCommands.focusRequests.empty());
+                                                 !inputWindowCommands.focusRequests.empty(),
+                                         mLastInputFlingerUpdateVsyncId,
+                                         mLastInputFlingerUpdateTimestamp);
         } else {
             // If there are listeners but no changes to input windows, call the listeners
             // immediately.
@@ -6105,6 +6109,29 @@
 
     result.append(mTimeStats->miniDump());
     result.append("\n");
+
+    result.append("Window Infos:\n");
+    StringAppendF(&result, "  input flinger update vsync id: %" PRId64 "\n",
+                  mLastInputFlingerUpdateVsyncId.value);
+    StringAppendF(&result, "  input flinger update timestamp (ns): %" PRId64 "\n",
+                  mLastInputFlingerUpdateTimestamp);
+    result.append("\n");
+
+    if (int64_t unsentVsyncId = mWindowInfosListenerInvoker->getUnsentMessageVsyncId().value;
+        unsentVsyncId != -1) {
+        StringAppendF(&result, "  unsent input flinger update vsync id: %" PRId64 "\n",
+                      unsentVsyncId);
+        StringAppendF(&result, "  unsent input flinger update timestamp (ns): %" PRId64 "\n",
+                      mWindowInfosListenerInvoker->getUnsentMessageTimestamp());
+        result.append("\n");
+    }
+
+    if (uint32_t pendingMessages = mWindowInfosListenerInvoker->getPendingMessageCount();
+        pendingMessages != 0) {
+        StringAppendF(&result, "  pending input flinger calls: %" PRIu32 "\n",
+                      mWindowInfosListenerInvoker->getPendingMessageCount());
+        result.append("\n");
+    }
 }
 
 mat4 SurfaceFlinger::calculateColorMatrix(float saturation) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index cd7659b..8eaa1c7 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -717,7 +717,7 @@
     void updateLayerHistory(const frontend::LayerSnapshot& snapshot);
     frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
 
-    void updateInputFlinger();
+    void updateInputFlinger(VsyncId);
     void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
     void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos,
                           std::vector<gui::DisplayInfo>& outDisplayInfos);
@@ -1248,6 +1248,9 @@
 
     VsyncId mLastCommittedVsyncId;
 
+    VsyncId mLastInputFlingerUpdateVsyncId;
+    nsecs_t mLastInputFlingerUpdateTimestamp;
+
     // If blurs should be enabled on this device.
     bool mSupportsBlur = false;
     std::atomic<uint32_t> mFrameMissedCount = 0;
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index 856fbbb..2b62638 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -16,6 +16,7 @@
 
 #include <ftl/small_vector.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/WindowInfosUpdate.h>
 
 #include "WindowInfosListenerInvoker.h"
 
@@ -86,11 +87,12 @@
 
 void WindowInfosListenerInvoker::windowInfosChanged(
         std::vector<WindowInfo> windowInfos, std::vector<DisplayInfo> displayInfos,
-        WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall) {
+        WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall, VsyncId vsyncId,
+        nsecs_t timestamp) {
     reportedListeners.insert(sp<WindowInfosListenerInvoker>::fromExisting(this));
     auto callListeners = [this, windowInfos = std::move(windowInfos),
-                          displayInfos = std::move(displayInfos)](
-                                 WindowInfosReportedListenerSet reportedListeners) mutable {
+                          displayInfos = std::move(displayInfos), vsyncId,
+                          timestamp](WindowInfosReportedListenerSet reportedListeners) mutable {
         WindowInfosListenerVector windowInfosListeners;
         {
             std::scoped_lock lock(mListenersMutex);
@@ -103,6 +105,9 @@
                 sp<WindowInfosReportedListenerInvoker>::make(windowInfosListeners,
                                                              std::move(reportedListeners));
 
+        gui::WindowInfosUpdate update(std::move(windowInfos), std::move(displayInfos),
+                                      vsyncId.value, timestamp);
+
         for (const auto& listener : windowInfosListeners) {
             sp<IBinder> asBinder = IInterface::asBinder(listener);
 
@@ -111,8 +116,7 @@
             // calling onWindowInfosReported.
             asBinder->linkToDeath(reportedInvoker);
 
-            auto status =
-                    listener->onWindowInfosChanged(windowInfos, displayInfos, reportedInvoker);
+            auto status = listener->onWindowInfosChanged(update, reportedInvoker);
             if (!status.isOk()) {
                 reportedInvoker->onWindowInfosReported();
             }
@@ -129,11 +133,15 @@
         // to reduce the amount of binder memory used.
         if (mActiveMessageCount > 0 && !forceImmediateCall) {
             mWindowInfosChangedDelayed = std::move(callListeners);
+            mUnsentVsyncId = vsyncId;
+            mUnsentTimestamp = timestamp;
             mReportedListenersDelayed.merge(reportedListeners);
             return;
         }
 
         mWindowInfosChangedDelayed = nullptr;
+        mUnsentVsyncId = {-1};
+        mUnsentTimestamp = -1;
         reportedListeners.merge(mReportedListenersDelayed);
         mActiveMessageCount++;
     }
@@ -154,6 +162,8 @@
         mActiveMessageCount++;
         callListeners = std::move(mWindowInfosChangedDelayed);
         mWindowInfosChangedDelayed = nullptr;
+        mUnsentVsyncId = {-1};
+        mUnsentTimestamp = -1;
         reportedListeners = std::move(mReportedListenersDelayed);
         mReportedListenersDelayed.clear();
     }
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index 4da9828..e35d056 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -26,6 +26,8 @@
 #include <gui/SpHash.h>
 #include <utils/Mutex.h>
 
+#include "scheduler/VsyncId.h"
+
 namespace android {
 
 using WindowInfosReportedListenerSet =
@@ -40,10 +42,25 @@
 
     void windowInfosChanged(std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>,
                             WindowInfosReportedListenerSet windowInfosReportedListeners,
-                            bool forceImmediateCall);
+                            bool forceImmediateCall, VsyncId vsyncId, nsecs_t timestamp);
 
     binder::Status onWindowInfosReported() override;
 
+    VsyncId getUnsentMessageVsyncId() {
+        std::scoped_lock lock(mMessagesMutex);
+        return mUnsentVsyncId;
+    }
+
+    nsecs_t getUnsentMessageTimestamp() {
+        std::scoped_lock lock(mMessagesMutex);
+        return mUnsentTimestamp;
+    }
+
+    uint32_t getPendingMessageCount() {
+        std::scoped_lock lock(mMessagesMutex);
+        return mActiveMessageCount;
+    }
+
 protected:
     void binderDied(const wp<IBinder>& who) override;
 
@@ -58,6 +75,8 @@
     uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0;
     std::function<void(WindowInfosReportedListenerSet)> mWindowInfosChangedDelayed
             GUARDED_BY(mMessagesMutex);
+    VsyncId mUnsentVsyncId GUARDED_BY(mMessagesMutex) = {-1};
+    nsecs_t mUnsentTimestamp GUARDED_BY(mMessagesMutex) = -1;
     WindowInfosReportedListenerSet mReportedListenersDelayed;
 };
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index c1bab0e..4d13aca 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -590,7 +590,7 @@
         mFlinger->binderDied(display);
         mFlinger->onFirstRef();
 
-        mFlinger->updateInputFlinger();
+        mFlinger->updateInputFlinger(VsyncId{0});
         mFlinger->updateCursorAsync();
 
         mutableScheduler().setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral<nsecs_t>(),
diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
index f4a8f03..3f27360 100644
--- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp
+++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
@@ -16,6 +16,7 @@
 
 #include <gtest/gtest.h>
 #include <gui/SurfaceComposerClient.h>
+#include <gui/WindowInfosUpdate.h>
 #include <private/android_filesystem_config.h>
 #include <cstdint>
 #include <future>
@@ -41,9 +42,8 @@
         WindowInfosListener(WindowInfosPredicate predicate, std::promise<void>& promise)
               : mPredicate(std::move(predicate)), mPromise(promise) {}
 
-        void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
-                                  const std::vector<DisplayInfo>&) override {
-            if (mPredicate(windowInfos)) {
+        void onWindowInfosChanged(const gui::WindowInfosUpdate& update) override {
+            if (mPredicate(update.windowInfos)) {
                 mPromise.set_value();
             }
         }