Recover from buffer stuffing for canned animations

Buffer stuffing occurs when SurfaceFlinger misses a frame, but the
client continues to produce buffers at the same rate, causing a
greater risk for jank to occur. Recovery is achieved for canned
animations by adjusting the animation timeline on the client side so
that SurfaceFlinger is no longer behind.

Use SF backdoor command 1045 to inject jank.
Usage: adb shell service call SurfaceFlinger 1045 f 1

Bug: b/294922229
Test: atest EventThreadTest
Test: presubmit, manually check perfetto traces
Flag: android.view.flags.buffer_stuffing_recovery
Change-Id: I38f0eb3d6ef1331e07d6022fa3a0e16c556ba06f
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 47b811b..e78bd9a 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -29,6 +29,7 @@
 #include <cinttypes>
 #include <numeric>
 #include <unordered_set>
+#include <vector>
 
 #include "../Jank/JankTracker.h"
 
@@ -997,6 +998,11 @@
     finalizeCurrentDisplayFrame();
 }
 
+const std::vector<std::shared_ptr<frametimeline::SurfaceFrame>>& FrameTimeline::getPresentFrames()
+        const {
+    return mPresentFrames;
+}
+
 void FrameTimeline::onCommitNotComposited() {
     SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
@@ -1492,6 +1498,7 @@
         mPendingPresentFences.erase(mPendingPresentFences.begin());
     }
 
+    mPresentFrames.clear();
     for (size_t i = 0; i < mPendingPresentFences.size(); i++) {
         const auto& pendingPresentFence = mPendingPresentFences[i];
         nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
@@ -1504,6 +1511,13 @@
 
         auto& displayFrame = pendingPresentFence.second;
         displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
+
+        // Surface frames have been jank classified and can be provided to caller
+        // to detect if buffer stuffing is occurring.
+        for (const auto& frame : displayFrame->getSurfaceFrames()) {
+            mPresentFrames.push_back(frame);
+        }
+
         mPreviousPredictionPresentTime =
                 displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
                                     mPreviousPredictionPresentTime, mFilterFramesBeforeTraceStarts);
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index cffb61e..1e0940f 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -323,6 +323,11 @@
     virtual void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                               const std::shared_ptr<FenceTime>& gpuFence) = 0;
 
+    // Provides surface frames that have already been jank classified in the most recent
+    // flush of pending present fences. This allows buffer stuffing detection from SF.
+    virtual const std::vector<std::shared_ptr<frametimeline::SurfaceFrame>>& getPresentFrames()
+            const = 0;
+
     // Tells FrameTimeline that a frame was committed but not composited. This is used to flush
     // all the associated surface frames.
     virtual void onCommitNotComposited() = 0;
@@ -497,6 +502,8 @@
     void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate, Fps renderRate) override;
     void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                       const std::shared_ptr<FenceTime>& gpuFence = FenceTime::NO_FENCE) override;
+    const std::vector<std::shared_ptr<frametimeline::SurfaceFrame>>& getPresentFrames()
+            const override;
     void onCommitNotComposited() override;
     void parseArgs(const Vector<String16>& args, std::string& result) override;
     void setMaxDisplayFrames(uint32_t size) override;
@@ -543,6 +550,9 @@
     // display frame, this is a good starting size for the vector so that we can avoid the
     // internal vector resizing that happens with push_back.
     static constexpr uint32_t kNumSurfaceFramesInitial = 10;
+    // Presented surface frames that have been jank classified and can
+    // indicate of potential buffer stuffing.
+    std::vector<std::shared_ptr<frametimeline::SurfaceFrame>> mPresentFrames;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index e385f18..24d387a 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -44,10 +44,8 @@
 
 #include <common/FlagManager.h>
 #include <scheduler/VsyncConfig.h>
-#include "DisplayHardware/DisplayMode.h"
 #include "FrameTimeline.h"
 #include "VSyncDispatch.h"
-#include "VSyncTracker.h"
 
 #include "EventThread.h"
 
@@ -472,6 +470,14 @@
     mCondition.notify_all();
 }
 
+// Merge lists of buffer stuffed Uids
+void EventThread::addBufferStuffedUids(BufferStuffingMap bufferStuffedUids) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    for (auto& [uid, count] : bufferStuffedUids) {
+        mBufferStuffedUids.emplace_or_replace(uid, count);
+    }
+}
+
 void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
     DisplayEventConsumers consumers;
 
@@ -701,6 +707,10 @@
 
 void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event,
                                 const DisplayEventConsumers& consumers) {
+    // List of Uids that have been sent vsync data with queued buffer count.
+    // Used to keep track of which Uids can be removed from the map of
+    // buffer stuffed clients.
+    ftl::SmallVector<uid_t, 10> uidsPostedQueuedBuffers;
     for (const auto& consumer : consumers) {
         DisplayEventReceiver::Event copy = event;
         if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
@@ -710,6 +720,13 @@
                                   event.vsync.vsyncData.preferredExpectedPresentationTime(),
                                   event.vsync.vsyncData.preferredDeadlineTimestamp());
         }
+        auto it = mBufferStuffedUids.find(consumer->mOwnerUid);
+        if (it != mBufferStuffedUids.end()) {
+            copy.vsync.vsyncData.numberQueuedBuffers = it->second;
+            uidsPostedQueuedBuffers.emplace_back(consumer->mOwnerUid);
+        } else {
+            copy.vsync.vsyncData.numberQueuedBuffers = 0;
+        }
         switch (consumer->postEvent(copy)) {
             case NO_ERROR:
                 break;
@@ -725,6 +742,12 @@
                 removeDisplayEventConnectionLocked(consumer);
         }
     }
+    // The clients that have already received the queued buffer count
+    // can be removed from the buffer stuffed Uid list to avoid
+    // being sent duplicate messages.
+    for (auto uid : uidsPostedQueuedBuffers) {
+        mBufferStuffedUids.erase(uid);
+    }
     if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC &&
         FlagManager::getInstance().vrr_config()) {
         mLastCommittedVsyncTime =
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index c3c7eb0..02e2592 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -32,7 +32,6 @@
 #include <thread>
 #include <vector>
 
-#include "DisplayHardware/DisplayMode.h"
 #include "TracedOrdinal.h"
 #include "VSyncDispatch.h"
 #include "VsyncSchedule.h"
@@ -55,6 +54,7 @@
 // ---------------------------------------------------------------------------
 
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
+using BufferStuffingMap = ftl::SmallMap<uid_t, uint32_t, 10>;
 
 enum class VSyncRequest {
     None = -2,
@@ -134,6 +134,10 @@
 
     virtual void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
                                      int32_t maxLevel) = 0;
+
+    // An elevated number of queued buffers in the server is detected. This propagates a
+    // flag to Choreographer indicating that buffer stuffing recovery should begin.
+    virtual void addBufferStuffedUids(BufferStuffingMap bufferStuffedUids);
 };
 
 struct IEventThreadCallback {
@@ -184,6 +188,8 @@
     void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
                              int32_t maxLevel) override;
 
+    void addBufferStuffedUids(BufferStuffingMap bufferStuffedUids) override;
+
 private:
     friend EventThreadTest;
 
@@ -224,6 +230,10 @@
     scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
     frametimeline::TokenManager* const mTokenManager;
 
+    // All consumers that need to recover from buffer stuffing and the number
+    // of their queued buffers.
+    BufferStuffingMap mBufferStuffedUids GUARDED_BY(mMutex);
+
     IEventThreadCallback& mCallback;
 
     std::thread mThread;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index b83ff19..b984e92 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -940,6 +940,11 @@
     return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides);
 }
 
+void Scheduler::addBufferStuffedUids(BufferStuffingMap bufferStuffedUids) {
+    if (!mRenderEventThread) return;
+    mRenderEventThread->addBufferStuffedUids(std::move(bufferStuffedUids));
+}
+
 void Scheduler::promotePacesetterDisplay(PhysicalDisplayId pacesetterId, PromotionParams params) {
     std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
     {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index c88b563..1842426 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -42,7 +42,6 @@
 #include <ui/DisplayId.h>
 #include <ui/DisplayMap.h>
 
-#include "Display/DisplayModeRequest.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "ISchedulerCallback.h"
@@ -332,6 +331,10 @@
         mPacesetterFrameDurationFractionToSkip = frameDurationFraction;
     }
 
+    // Propagates a flag to the EventThread indicating that buffer stuffing
+    // recovery should begin.
+    void addBufferStuffedUids(BufferStuffingMap bufferStuffedUids);
+
 private:
     friend class TestableScheduler;
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d35a76a..c0eade8 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -64,11 +64,13 @@
 #include <ftl/concat.h>
 #include <ftl/fake_guard.h>
 #include <ftl/future.h>
+#include <ftl/small_map.h>
 #include <ftl/unit.h>
 #include <gui/AidlUtil.h>
 #include <gui/BufferQueue.h>
 #include <gui/DebugEGLImageTracker.h>
 #include <gui/IProducerListener.h>
+#include <gui/JankInfo.h>
 #include <gui/LayerMetadata.h>
 #include <gui/LayerState.h>
 #include <gui/Surface.h>
@@ -3066,12 +3068,40 @@
 
     const TimePoint presentTime = TimePoint::now();
 
+    // The Uids of layer owners that are in buffer stuffing mode, and their elevated
+    // buffer counts. Messages to start recovery are sent exclusively to these Uids.
+    BufferStuffingMap bufferStuffedUids;
+
     // Set presentation information before calling Layer::releasePendingBuffer, such that jank
     // information from previous' frame classification is already available when sending jank info
     // to clients, so they get jank classification as early as possible.
     mFrameTimeline->setSfPresent(presentTime.ns(), pacesetterPresentFenceTime,
                                  pacesetterGpuCompositionDoneFenceTime);
 
+    // Find and register any layers that are in buffer stuffing mode
+    const auto& presentFrames = mFrameTimeline->getPresentFrames();
+
+    for (const auto& frame : presentFrames) {
+        const auto& layer = mLayerLifecycleManager.getLayerFromId(frame->getLayerId());
+        if (!layer) continue;
+        uint32_t numberQueuedBuffers = layer->pendingBuffers ? layer->pendingBuffers->load() : 0;
+        int32_t jankType = frame->getJankType().value_or(JankType::None);
+        if (jankType & JankType::BufferStuffing &&
+            layer->flags & layer_state_t::eRecoverableFromBufferStuffing) {
+            auto [it, wasEmplaced] =
+                    bufferStuffedUids.try_emplace(layer->ownerUid.val(), numberQueuedBuffers);
+            // Update with maximum number of queued buffers, allows clients drawing
+            // multiple windows to account for the most severely stuffed window
+            if (!wasEmplaced && it->second < numberQueuedBuffers) {
+                it->second = numberQueuedBuffers;
+            }
+        }
+    }
+
+    if (!bufferStuffedUids.empty()) {
+        mScheduler->addBufferStuffedUids(std::move(bufferStuffedUids));
+    }
+
     // We use the CompositionEngine::getLastFrameRefreshTimestamp() which might
     // be sampled a little later than when we started doing work for this frame,
     // but that should be okay since CompositorTiming has snapping logic.
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 31218ed..5d83315 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1382,6 +1382,8 @@
     // Flag used to set override desired display mode from backdoor
     bool mDebugDisplayModeSetByBackdoor = false;
 
+    // Tracks the number of maximum queued buffers by layer owner Uid.
+    using BufferStuffingMap = ftl::SmallMap<uid_t, uint32_t, 10>;
     BufferCountTracker mBufferCountTracker;
 
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 625d2e6..268a6c4 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -23,6 +23,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <gui/DisplayEventReceiver.h>
 #include <log/log.h>
 #include <scheduler/VsyncConfig.h>
 #include <utils/Errors.h>
@@ -111,6 +112,8 @@
     void expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime);
     void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                             std::vector<FrameRateOverride>);
+    void expectQueuedBufferCountReceivedByConnection(
+            ConnectionEventRecorder& connectionEventRecorder, uint32_t expectedBufferCount);
 
     void onVSyncEvent(nsecs_t timestamp, nsecs_t expectedPresentationTime,
                       nsecs_t deadlineTimestamp) {
@@ -144,6 +147,7 @@
     sp<MockEventThreadConnection> mConnection;
     sp<MockEventThreadConnection> mThrottledConnection;
     std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
+    std::vector<ConnectionEventRecorder*> mBufferStuffedConnectionRecorders;
 
     std::chrono::nanoseconds mVsyncPeriod;
 
@@ -376,6 +380,14 @@
     EXPECT_EQ(expectedDisplayId, event.header.displayId);
 }
 
+void EventThreadTest::expectQueuedBufferCountReceivedByConnection(
+        ConnectionEventRecorder& connectionEventRecorder, uint32_t expectedBufferCount) {
+    auto args = connectionEventRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    const auto& event = std::get<0>(args.value());
+    EXPECT_EQ(expectedBufferCount, event.vsync.vsyncData.numberQueuedBuffers);
+}
+
 namespace {
 
 using namespace testing;
@@ -868,6 +880,63 @@
     EXPECT_EQ(HDCP_V2, event.hdcpLevelsChange.maxLevel);
 }
 
+TEST_F(EventThreadTest, connectionReceivesBufferStuffing) {
+    setupEventThread();
+
+    // Create a connection that will experience buffer stuffing.
+    ConnectionEventRecorder stuffedConnectionEventRecorder{0};
+    sp<MockEventThreadConnection> stuffedConnection =
+            createConnection(stuffedConnectionEventRecorder,
+                             gui::ISurfaceComposer::EventRegistration::modeChanged |
+                                     gui::ISurfaceComposer::EventRegistration::frameRateOverride,
+                             111);
+
+    // Add a connection and buffer count to the list of stuffed Uids that will receive
+    // data in the next vsync event.
+    BufferStuffingMap bufferStuffedUids;
+    bufferStuffedUids.try_emplace(stuffedConnection->mOwnerUid, 3);
+    mThread->addBufferStuffedUids(bufferStuffedUids);
+    mBufferStuffedConnectionRecorders.emplace_back(&stuffedConnectionEventRecorder);
+
+    // Signal that we want the next vsync event to be posted to two connections.
+    mThread->requestNextVsync(mConnection);
+    mThread->requestNextVsync(stuffedConnection);
+    onVSyncEvent(123, 456, 789);
+
+    // Vsync event data contains number of queued buffers.
+    expectQueuedBufferCountReceivedByConnection(mConnectionEventCallRecorder, 0);
+    expectQueuedBufferCountReceivedByConnection(stuffedConnectionEventRecorder, 3);
+}
+
+TEST_F(EventThreadTest, connectionsWithSameUidReceiveBufferStuffing) {
+    setupEventThread();
+
+    // Create a connection with the same Uid as another connection.
+    ConnectionEventRecorder secondConnectionEventRecorder{0};
+    sp<MockEventThreadConnection> secondConnection =
+            createConnection(secondConnectionEventRecorder,
+                             gui::ISurfaceComposer::EventRegistration::modeChanged |
+                                     gui::ISurfaceComposer::EventRegistration::frameRateOverride,
+                             mConnectionUid);
+
+    // Add connection Uid and buffer count to the list of stuffed Uids that will receive
+    // data in the next vsync event.
+    BufferStuffingMap bufferStuffedUids;
+    bufferStuffedUids.try_emplace(mConnectionUid, 3);
+    mThread->addBufferStuffedUids(bufferStuffedUids);
+    mBufferStuffedConnectionRecorders.emplace_back(&mConnectionEventCallRecorder);
+    mBufferStuffedConnectionRecorders.emplace_back(&secondConnectionEventRecorder);
+
+    // Signal that we want the next vsync event to be posted to two connections.
+    mThread->requestNextVsync(mConnection);
+    mThread->requestNextVsync(secondConnection);
+    onVSyncEvent(123, 456, 789);
+
+    // Vsync event data contains number of queued buffers.
+    expectQueuedBufferCountReceivedByConnection(mConnectionEventCallRecorder, 3);
+    expectQueuedBufferCountReceivedByConnection(secondConnectionEventRecorder, 3);
+}
+
 } // namespace
 } // namespace android
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 7398cbe..c49bd30 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -52,6 +52,7 @@
     MOCK_METHOD(void, onHdcpLevelsChanged,
                 (PhysicalDisplayId displayId, int32_t connectedLevel, int32_t maxLevel),
                 (override));
+    MOCK_METHOD(void, addBufferStuffedUids, (BufferStuffingMap), (override));
 };
 
 } // namespace android::mock