Merge "ANDROID: binder: prevent double flushing when free buffer" into sc-dev
diff --git a/include/input/LatencyStatistics.h b/include/input/LatencyStatistics.h
deleted file mode 100644
index bd86266..0000000
--- a/include/input/LatencyStatistics.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _UI_INPUT_STATISTICS_H
-#define _UI_INPUT_STATISTICS_H
-
-#include <android-base/chrono_utils.h>
-
-#include <stddef.h>
-
-namespace android {
-
-class LatencyStatistics {
-private:
-    /* Minimum sample recorded */
-    float mMin;
-    /* Maximum sample recorded */
-    float mMax;
-    /* Sum of all samples recorded */
-    float mSum;
-    /* Sum of all the squares of samples recorded */
-    float mSum2;
-    /* Count of all samples recorded */
-    size_t mCount;
-    /* The last time statistics were reported */
-    std::chrono::steady_clock::time_point mLastReportTime;
-    /* Statistics Report Frequency */
-    const std::chrono::seconds mReportPeriod;
-
-public:
-    LatencyStatistics(std::chrono::seconds period);
-
-    void addValue(float);
-    void reset();
-    bool shouldReport();
-
-    float getMean();
-    float getMin();
-    float getMax();
-    float getStDev();
-    size_t getCount();
-};
-
-} // namespace android
-
-#endif // _UI_INPUT_STATISTICS_H
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 6f79f4b..a63ec8f 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -49,7 +49,6 @@
         "Keyboard.cpp",
         "KeyCharacterMap.cpp",
         "KeyLayoutMap.cpp",
-        "LatencyStatistics.cpp",
         "PropertyMap.cpp",
         "TouchVideoFrame.cpp",
         "VelocityControl.cpp",
diff --git a/libs/input/LatencyStatistics.cpp b/libs/input/LatencyStatistics.cpp
deleted file mode 100644
index 394da22..0000000
--- a/libs/input/LatencyStatistics.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <input/LatencyStatistics.h>
-
-#include <android-base/chrono_utils.h>
-
-#include <cmath>
-#include <limits>
-
-namespace android {
-
-LatencyStatistics::LatencyStatistics(std::chrono::seconds period) : mReportPeriod(period) {
-    reset();
-}
-
-/**
- * Add a raw value to the statistics
- */
-void LatencyStatistics::addValue(float value) {
-    if (value < mMin) {
-        mMin = value;
-    }
-    if (value > mMax) {
-        mMax = value;
-    }
-    mSum += value;
-    mSum2 += value * value;
-    mCount++;
-}
-
-/**
- * Get the mean. Should not be called if no samples have been added.
- */
-float LatencyStatistics::getMean() {
-    return mSum / mCount;
-}
-
-/**
- * Get the standard deviation. Should not be called if no samples have been added.
- */
-float LatencyStatistics::getStDev() {
-    float mean = getMean();
-    return sqrt(mSum2 / mCount - mean * mean);
-}
-
-float LatencyStatistics::getMin() {
-    return mMin;
-}
-
-float LatencyStatistics::getMax() {
-    return mMax;
-}
-
-size_t LatencyStatistics::getCount() {
-    return mCount;
-}
-
-/**
- * Reset internal state. The variable 'when' is the time when the data collection started.
- * Call this to start a new data collection window.
- */
-void LatencyStatistics::reset() {
-    mMax = std::numeric_limits<float>::lowest();
-    mMin = std::numeric_limits<float>::max();
-    mSum = 0;
-    mSum2 = 0;
-    mCount = 0;
-    mLastReportTime = std::chrono::steady_clock::now();
-}
-
-bool LatencyStatistics::shouldReport() {
-    std::chrono::duration timeSinceReport = std::chrono::steady_clock::now() - mLastReportTime;
-    return mCount != 0 && timeSinceReport >= mReportPeriod;
-}
-
-} // namespace android
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 767878b..6ffc6a8 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -19,7 +19,6 @@
         "InputEvent_test.cpp",
         "InputPublisherAndConsumer_test.cpp",
         "InputWindow_test.cpp",
-        "LatencyStatistics_test.cpp",
         "TouchVideoFrame_test.cpp",
         "VelocityTracker_test.cpp",
         "VerifiedInputEvent_test.cpp",
diff --git a/libs/input/tests/LatencyStatistics_test.cpp b/libs/input/tests/LatencyStatistics_test.cpp
deleted file mode 100644
index eb12d4e..0000000
--- a/libs/input/tests/LatencyStatistics_test.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-#include <input/LatencyStatistics.h>
-#include <cmath>
-#include <limits>
-#include <thread>
-
-namespace android {
-namespace test {
-
-TEST(LatencyStatisticsTest, ResetStats) {
-    LatencyStatistics stats{5min};
-    stats.addValue(5.0);
-    stats.addValue(19.3);
-    stats.addValue(20);
-    stats.reset();
-
-    ASSERT_EQ(stats.getCount(), 0u);
-    ASSERT_EQ(std::isnan(stats.getStDev()), true);
-    ASSERT_EQ(std::isnan(stats.getMean()), true);
-}
-
-TEST(LatencyStatisticsTest, AddStatsValue) {
-    LatencyStatistics stats{5min};
-    stats.addValue(5.0);
-
-    ASSERT_EQ(stats.getMin(), 5.0);
-    ASSERT_EQ(stats.getMax(), 5.0);
-    ASSERT_EQ(stats.getCount(), 1u);
-    ASSERT_EQ(stats.getMean(), 5.0);
-    ASSERT_EQ(stats.getStDev(), 0.0);
-}
-
-TEST(LatencyStatisticsTest, AddMultipleStatsValue) {
-    LatencyStatistics stats{5min};
-    stats.addValue(4.0);
-    stats.addValue(6.0);
-    stats.addValue(8.0);
-    stats.addValue(10.0);
-
-    float stdev = stats.getStDev();
-
-    ASSERT_EQ(stats.getMin(), 4.0);
-    ASSERT_EQ(stats.getMax(), 10.0);
-    ASSERT_EQ(stats.getCount(), 4u);
-    ASSERT_EQ(stats.getMean(), 7.0);
-    ASSERT_EQ(stdev * stdev, 5.0);
-}
-
-TEST(LatencyStatisticsTest, ShouldReportStats) {
-    LatencyStatistics stats{0min};
-    stats.addValue(5.0);
-
-    std::this_thread::sleep_for(1us);
-
-    ASSERT_EQ(stats.shouldReport(), true);
-}
-
-} // namespace test
-} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 9b98a17..6612a93 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -63,11 +63,16 @@
         "libcutils",
         "libhidlbase",
         "libinput",
+        "libkll",
         "liblog",
+        "libprotobuf-cpp-lite",
         "libstatslog",
+        "libstatspull",
+        "libstatssocket",
         "libutils",
         "libui",
         "lib-platform-compat-native-api",
+        "server_configurable_flags",
     ],
     static_libs: [
         "libattestation",
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index ea37f4d..902bd0d 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -12,7 +12,10 @@
     srcs: [
         "InputDispatcher_benchmarks.cpp",
     ],
-    defaults: ["inputflinger_defaults"],
+    defaults: [
+        "inputflinger_defaults",
+        "libinputdispatcher_defaults",
+    ],
     shared_libs: [
         "libbase",
         "libbinder",
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 1b5f1ab..bc77b8a 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -16,9 +16,11 @@
 
 #include <benchmark/benchmark.h>
 
+#include <android/os/IInputConstants.h>
 #include <binder/Binder.h>
 #include "../dispatcher/InputDispatcher.h"
 
+using android::os::IInputConstants;
 using android::os::InputEventInjectionResult;
 using android::os::InputEventInjectionSync;
 
@@ -226,7 +228,7 @@
 
     ui::Transform identityTransform;
     MotionEvent event;
-    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+    event.initialize(IInputConstants::INVALID_INPUT_EVENT_ID, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
                      ADISPLAY_ID_DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
                      /* actionButton */ 0, /* flags */ 0,
                      /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
@@ -252,9 +254,9 @@
 
     const nsecs_t currentTime = now();
     // Define a valid motion event.
-    NotifyMotionArgs args(/* id */ 0, currentTime, currentTime, DEVICE_ID,
-                          AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, POLICY_FLAG_PASS_TO_USER,
-                          AMOTION_EVENT_ACTION_DOWN,
+    NotifyMotionArgs args(IInputConstants::INVALID_INPUT_EVENT_ID, currentTime, currentTime,
+                          DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                          POLICY_FLAG_PASS_TO_USER, AMOTION_EVENT_ACTION_DOWN,
                           /* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
                           MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                           pointerProperties, pointerCoords,
@@ -283,14 +285,12 @@
     for (auto _ : state) {
         // Send ACTION_DOWN
         motionArgs.action = AMOTION_EVENT_ACTION_DOWN;
-        motionArgs.id = 0;
         motionArgs.downTime = now();
         motionArgs.eventTime = motionArgs.downTime;
         dispatcher->notifyMotion(&motionArgs);
 
         // Send ACTION_UP
         motionArgs.action = AMOTION_EVENT_ACTION_UP;
-        motionArgs.id = 1;
         motionArgs.eventTime = now();
         dispatcher->notifyMotion(&motionArgs);
 
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 9750ef9..1b3888b 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -38,8 +38,11 @@
         "InjectionState.cpp",
         "InputDispatcher.cpp",
         "InputDispatcherFactory.cpp",
+        "InputEventTimeline.cpp",
         "InputState.cpp",
         "InputTarget.cpp",
+        "LatencyAggregator.cpp",
+        "LatencyTracker.cpp",
         "Monitor.cpp",
         "TouchState.cpp",
         "DragState.cpp",
@@ -54,11 +57,16 @@
         "libcrypto",
         "libcutils",
         "libinput",
+        "libkll",
         "liblog",
+        "libprotobuf-cpp-lite",
         "libstatslog",
+        "libstatspull",
+        "libstatssocket",
         "libui",
         "libutils",
         "lib-platform-compat-native-api",
+        "server_configurable_flags",
     ],
     static_libs: [
         "libattestation",
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index b5d3571..ebbd8e9 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -79,7 +79,7 @@
     explicit ConfigurationChangedEntry(int32_t id, nsecs_t eventTime);
     std::string getDescription() const override;
 
-    virtual ~ConfigurationChangedEntry();
+    ~ConfigurationChangedEntry() override;
 };
 
 struct DeviceResetEntry : EventEntry {
@@ -88,7 +88,7 @@
     DeviceResetEntry(int32_t id, nsecs_t eventTime, int32_t deviceId);
     std::string getDescription() const override;
 
-    virtual ~DeviceResetEntry();
+    ~DeviceResetEntry() override;
 };
 
 struct FocusEntry : EventEntry {
@@ -100,7 +100,7 @@
                const std::string& reason);
     std::string getDescription() const override;
 
-    virtual ~FocusEntry();
+    ~FocusEntry() override;
 };
 
 struct PointerCaptureChangedEntry : EventEntry {
@@ -109,7 +109,7 @@
     PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime, bool hasPointerCapture);
     std::string getDescription() const override;
 
-    virtual ~PointerCaptureChangedEntry();
+    ~PointerCaptureChangedEntry() override;
 };
 
 struct DragEntry : EventEntry {
@@ -153,7 +153,7 @@
     std::string getDescription() const override;
     void recycle();
 
-    virtual ~KeyEntry();
+    ~KeyEntry() override;
 };
 
 struct MotionEntry : EventEntry {
@@ -204,7 +204,7 @@
                 std::vector<float> values);
     std::string getDescription() const override;
 
-    virtual ~SensorEntry();
+    ~SensorEntry() override;
 };
 
 // Tracks the progress of dispatching a particular event to a particular connection.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 485a53a..790bd09 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -59,7 +59,6 @@
 #include <log/log.h>
 #include <log/log_event_list.h>
 #include <powermanager/PowerManager.h>
-#include <statslog.h>
 #include <unistd.h>
 #include <utils/Trace.h>
 
@@ -447,6 +446,56 @@
     return std::nullopt;
 }
 
+static bool shouldReportMetricsForConnection(const Connection& connection) {
+    // Do not keep track of gesture monitors. They receive every event and would disproportionately
+    // affect the statistics.
+    if (connection.monitor) {
+        return false;
+    }
+    // If the connection is experiencing ANR, let's skip it. We have separate ANR metrics
+    if (!connection.responsive) {
+        return false;
+    }
+    return true;
+}
+
+static bool shouldReportFinishedEvent(const DispatchEntry& dispatchEntry,
+                                      const Connection& connection) {
+    const EventEntry& eventEntry = *dispatchEntry.eventEntry;
+    const int32_t& inputEventId = eventEntry.id;
+    if (inputEventId != dispatchEntry.resolvedEventId) {
+        // Event was transmuted
+        return false;
+    }
+    if (inputEventId == android::os::IInputConstants::INVALID_INPUT_EVENT_ID) {
+        return false;
+    }
+    // Only track latency for events that originated from hardware
+    if (eventEntry.isSynthesized()) {
+        return false;
+    }
+    const EventEntry::Type& inputEventEntryType = eventEntry.type;
+    if (inputEventEntryType == EventEntry::Type::KEY) {
+        const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
+        if (keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) {
+            return false;
+        }
+    } else if (inputEventEntryType == EventEntry::Type::MOTION) {
+        const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+        if (motionEntry.action == AMOTION_EVENT_ACTION_CANCEL ||
+            motionEntry.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+            return false;
+        }
+    } else {
+        // Not a key or a motion
+        return false;
+    }
+    if (!shouldReportMetricsForConnection(connection)) {
+        return false;
+    }
+    return true;
+}
+
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
@@ -468,6 +517,8 @@
         mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
         mFocusedWindowRequestedPointerCapture(false),
         mWindowTokenWithPointerCapture(nullptr),
+        mLatencyAggregator(),
+        mLatencyTracker(&mLatencyAggregator),
         mCompatService(getCompatService()) {
     mLooper = new Looper(false);
     mReporter = createInputReporter();
@@ -2854,6 +2905,8 @@
                       "event",
                       connection->getInputChannelName().c_str());
 #endif
+                // We keep the 'resolvedEventId' here equal to the original 'motionEntry.id' because
+                // this is a one-to-one event conversion.
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
             }
 
@@ -2921,7 +2974,7 @@
 
     // Enqueue the dispatch entry.
     connection->outboundQueue.push_back(dispatchEntry.release());
-    traceOutboundQueueLength(connection);
+    traceOutboundQueueLength(*connection);
 }
 
 /**
@@ -3102,7 +3155,6 @@
                                                      motionEntry.downTime, motionEntry.eventTime,
                                                      motionEntry.pointerCount,
                                                      motionEntry.pointerProperties, usingCoords);
-                reportTouchEventForStatistics(motionEntry);
                 break;
             }
 
@@ -3176,13 +3228,13 @@
         connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
                                                     connection->outboundQueue.end(),
                                                     dispatchEntry));
-        traceOutboundQueueLength(connection);
+        traceOutboundQueueLength(*connection);
         connection->waitQueue.push_back(dispatchEntry);
         if (connection->responsive) {
             mAnrTracker.insert(dispatchEntry->timeoutTime,
                                connection->inputChannel->getConnectionToken());
         }
-        traceWaitQueueLength(connection);
+        traceWaitQueueLength(*connection);
     }
 }
 
@@ -3251,9 +3303,9 @@
 
     // Clear the dispatch queues.
     drainDispatchQueue(connection->outboundQueue);
-    traceOutboundQueueLength(connection);
+    traceOutboundQueueLength(*connection);
     drainDispatchQueue(connection->waitQueue);
-    traceWaitQueueLength(connection);
+    traceWaitQueueLength(*connection);
 
     // The connection appears to be unrecoverably broken.
     // Ignore already broken or zombie connections.
@@ -3317,7 +3369,14 @@
                 finishDispatchCycleLocked(currentTime, connection, finish.seq, finish.handled,
                                           finish.consumeTime);
             } else if (std::holds_alternative<InputPublisher::Timeline>(*result)) {
-                // TODO(b/167947340): Report this data to LatencyTracker
+                if (shouldReportMetricsForConnection(*connection)) {
+                    const InputPublisher::Timeline& timeline =
+                            std::get<InputPublisher::Timeline>(*result);
+                    mLatencyTracker
+                            .trackGraphicsLatency(timeline.inputEventId,
+                                                  connection->inputChannel->getConnectionToken(),
+                                                  std::move(timeline.graphicsTimeline));
+                }
             }
             gotOne = true;
         }
@@ -3826,6 +3885,12 @@
                                               args->xCursorPosition, args->yCursorPosition,
                                               args->downTime, args->pointerCount,
                                               args->pointerProperties, args->pointerCoords, 0, 0);
+        if (args->id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
+            IdGenerator::getSource(args->id) == IdGenerator::Source::INPUT_READER &&
+            !mInputFilterEnabled) {
+            const bool isDown = args->action == AMOTION_EVENT_ACTION_DOWN;
+            mLatencyTracker.trackListener(args->id, isDown, args->eventTime, args->readTime);
+        }
 
         needWake = enqueueInboundEventLocked(std::move(newEntry));
         mLock.unlock();
@@ -5049,6 +5114,8 @@
     dump += StringPrintf(INDENT2 "KeyRepeatDelay: %" PRId64 "ms\n", ns2ms(mConfig.keyRepeatDelay));
     dump += StringPrintf(INDENT2 "KeyRepeatTimeout: %" PRId64 "ms\n",
                          ns2ms(mConfig.keyRepeatTimeout));
+    dump += mLatencyTracker.dump(INDENT2);
+    dump += mLatencyAggregator.dump(INDENT2);
 }
 
 void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) {
@@ -5141,6 +5208,8 @@
         monitorsByDisplay[displayId].emplace_back(serverChannel, pid);
 
         mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);
+        ALOGI("Created monitor %s for display %" PRId32 ", gesture=%s, pid=%" PRId32, name.c_str(),
+              displayId, toString(isGestureMonitor), pid);
     }
 
     // Wake the looper because some connections have changed.
@@ -5622,7 +5691,12 @@
         ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
               ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
     }
-    reportDispatchStatistics(std::chrono::nanoseconds(eventDuration), *connection, handled);
+    if (shouldReportFinishedEvent(*dispatchEntry, *connection)) {
+        mLatencyTracker.trackFinishedEvent(dispatchEntry->eventEntry->id,
+                                           connection->inputChannel->getConnectionToken(),
+                                           dispatchEntry->deliveryTime, commandEntry->consumeTime,
+                                           finishTime);
+    }
 
     bool restartEvent;
     if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
@@ -5654,10 +5728,10 @@
                 processConnectionResponsiveLocked(*connection);
             }
         }
-        traceWaitQueueLength(connection);
+        traceWaitQueueLength(*connection);
         if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
             connection->outboundQueue.push_front(dispatchEntry);
-            traceOutboundQueueLength(connection);
+            traceOutboundQueueLength(*connection);
         } else {
             releaseDispatchEntry(dispatchEntry);
         }
@@ -5934,58 +6008,25 @@
     mLock.lock();
 }
 
-void InputDispatcher::reportDispatchStatistics(std::chrono::nanoseconds eventDuration,
-                                               const Connection& connection, bool handled) {
-    // TODO Write some statistics about how long we spend waiting.
-}
-
-/**
- * Report the touch event latency to the statsd server.
- * Input events are reported for statistics if:
- * - This is a touchscreen event
- * - InputFilter is not enabled
- * - Event is not injected or synthesized
- *
- * Statistics should be reported before calling addValue, to prevent a fresh new sample
- * from getting aggregated with the "old" data.
- */
-void InputDispatcher::reportTouchEventForStatistics(const MotionEntry& motionEntry)
-        REQUIRES(mLock) {
-    const bool reportForStatistics = (motionEntry.source == AINPUT_SOURCE_TOUCHSCREEN) &&
-            !(motionEntry.isSynthesized()) && !mInputFilterEnabled;
-    if (!reportForStatistics) {
-        return;
-    }
-
-    if (mTouchStatistics.shouldReport()) {
-        android::util::stats_write(android::util::TOUCH_EVENT_REPORTED, mTouchStatistics.getMin(),
-                                   mTouchStatistics.getMax(), mTouchStatistics.getMean(),
-                                   mTouchStatistics.getStDev(), mTouchStatistics.getCount());
-        mTouchStatistics.reset();
-    }
-    const float latencyMicros = nanoseconds_to_microseconds(now() - motionEntry.eventTime);
-    mTouchStatistics.addValue(latencyMicros);
-}
-
 void InputDispatcher::traceInboundQueueLengthLocked() {
     if (ATRACE_ENABLED()) {
         ATRACE_INT("iq", mInboundQueue.size());
     }
 }
 
-void InputDispatcher::traceOutboundQueueLength(const sp<Connection>& connection) {
+void InputDispatcher::traceOutboundQueueLength(const Connection& connection) {
     if (ATRACE_ENABLED()) {
         char counterName[40];
-        snprintf(counterName, sizeof(counterName), "oq:%s", connection->getWindowName().c_str());
-        ATRACE_INT(counterName, connection->outboundQueue.size());
+        snprintf(counterName, sizeof(counterName), "oq:%s", connection.getWindowName().c_str());
+        ATRACE_INT(counterName, connection.outboundQueue.size());
     }
 }
 
-void InputDispatcher::traceWaitQueueLength(const sp<Connection>& connection) {
+void InputDispatcher::traceWaitQueueLength(const Connection& connection) {
     if (ATRACE_ENABLED()) {
         char counterName[40];
-        snprintf(counterName, sizeof(counterName), "wq:%s", connection->getWindowName().c_str());
-        ATRACE_INT(counterName, connection->waitQueue.size());
+        snprintf(counterName, sizeof(counterName), "wq:%s", connection.getWindowName().c_str());
+        ATRACE_INT(counterName, connection.waitQueue.size());
     }
 }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 6edc5f1..bb3f3e6 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -29,6 +29,8 @@
 #include "InputState.h"
 #include "InputTarget.h"
 #include "InputThread.h"
+#include "LatencyAggregator.h"
+#include "LatencyTracker.h"
 #include "Monitor.h"
 #include "TouchState.h"
 #include "TouchedWindow.h"
@@ -39,7 +41,6 @@
 #include <input/InputApplication.h>
 #include <input/InputTransport.h>
 #include <input/InputWindow.h>
-#include <input/LatencyStatistics.h>
 #include <limits.h>
 #include <stddef.h>
 #include <ui/Region.h>
@@ -636,15 +637,11 @@
     void doOnPointerDownOutsideFocusLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
 
     // Statistics gathering.
-    static constexpr std::chrono::duration TOUCH_STATS_REPORT_PERIOD = 5min;
-    LatencyStatistics mTouchStatistics{TOUCH_STATS_REPORT_PERIOD};
-
-    void reportTouchEventForStatistics(const MotionEntry& entry);
-    void reportDispatchStatistics(std::chrono::nanoseconds eventDuration,
-                                  const Connection& connection, bool handled);
+    LatencyAggregator mLatencyAggregator GUARDED_BY(mLock);
+    LatencyTracker mLatencyTracker GUARDED_BY(mLock);
     void traceInboundQueueLengthLocked() REQUIRES(mLock);
-    void traceOutboundQueueLength(const sp<Connection>& connection);
-    void traceWaitQueueLength(const sp<Connection>& connection);
+    void traceOutboundQueueLength(const Connection& connection);
+    void traceWaitQueueLength(const Connection& connection);
 
     sp<InputReporterInterface> mReporter;
     sp<com::android::internal::compat::IPlatformCompatNative> mCompatService;
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.cpp b/services/inputflinger/dispatcher/InputEventTimeline.cpp
new file mode 100644
index 0000000..3edb638
--- /dev/null
+++ b/services/inputflinger/dispatcher/InputEventTimeline.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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 "InputEventTimeline.h"
+
+namespace android::inputdispatcher {
+
+ConnectionTimeline::ConnectionTimeline(nsecs_t deliveryTime, nsecs_t consumeTime,
+                                       nsecs_t finishTime)
+      : deliveryTime(deliveryTime),
+        consumeTime(consumeTime),
+        finishTime(finishTime),
+        mHasDispatchTimeline(true) {}
+
+ConnectionTimeline::ConnectionTimeline(std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline)
+      : graphicsTimeline(std::move(graphicsTimeline)), mHasGraphicsTimeline(true) {}
+
+bool ConnectionTimeline::isComplete() const {
+    return mHasDispatchTimeline && mHasGraphicsTimeline;
+}
+
+bool ConnectionTimeline::setDispatchTimeline(nsecs_t inDeliveryTime, nsecs_t inConsumeTime,
+                                             nsecs_t inFinishTime) {
+    if (mHasDispatchTimeline) {
+        return false;
+    }
+    deliveryTime = inDeliveryTime;
+    consumeTime = inConsumeTime;
+    finishTime = inFinishTime;
+    mHasDispatchTimeline = true;
+    return true;
+}
+
+bool ConnectionTimeline::setGraphicsTimeline(std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
+    if (mHasGraphicsTimeline) {
+        return false;
+    }
+    graphicsTimeline = std::move(timeline);
+    mHasGraphicsTimeline = true;
+    return true;
+}
+
+bool ConnectionTimeline::operator==(const ConnectionTimeline& rhs) const {
+    return deliveryTime == rhs.deliveryTime && consumeTime == rhs.consumeTime &&
+            finishTime == rhs.finishTime && graphicsTimeline == rhs.graphicsTimeline &&
+            mHasDispatchTimeline == rhs.mHasDispatchTimeline &&
+            mHasGraphicsTimeline == rhs.mHasGraphicsTimeline;
+}
+
+bool ConnectionTimeline::operator!=(const ConnectionTimeline& rhs) const {
+    return !operator==(rhs);
+}
+
+InputEventTimeline::InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime)
+      : isDown(isDown), eventTime(eventTime), readTime(readTime) {}
+
+bool InputEventTimeline::operator==(const InputEventTimeline& rhs) const {
+    if (connectionTimelines.size() != rhs.connectionTimelines.size()) {
+        return false;
+    }
+    for (const auto& [connectionToken, connectionTimeline] : connectionTimelines) {
+        auto it = rhs.connectionTimelines.find(connectionToken);
+        if (it == rhs.connectionTimelines.end()) {
+            return false;
+        }
+        if (connectionTimeline != it->second) {
+            return false;
+        }
+    }
+    return isDown == rhs.isDown && eventTime == rhs.eventTime && readTime == rhs.readTime;
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.h b/services/inputflinger/dispatcher/InputEventTimeline.h
new file mode 100644
index 0000000..77b8472
--- /dev/null
+++ b/services/inputflinger/dispatcher/InputEventTimeline.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H
+#define _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H
+
+#include <binder/IBinder.h>
+#include <input/Input.h>
+#include <unordered_map>
+
+namespace android {
+
+namespace inputdispatcher {
+
+/**
+ * Describes the input event timeline for each connection.
+ * An event with the same inputEventId can go to more than 1 connection simultaneously.
+ * For each connection that the input event goes to, there will be a separate ConnectionTimeline
+ * created.
+ * To create a complete ConnectionTimeline, we must receive two calls:
+ * 1) setDispatchTimeline
+ * 2) setGraphicsTimeline
+ *
+ * In a typical scenario, the dispatch timeline is known first. Later, if a frame is produced, the
+ * graphics timeline is available.
+ */
+struct ConnectionTimeline {
+    // DispatchTimeline
+    nsecs_t deliveryTime; // time at which the event was sent to the receiver
+    nsecs_t consumeTime;  // time at which the receiver read the event
+    nsecs_t finishTime;   // time at which the finish event was received
+    // GraphicsTimeline
+    std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+
+    ConnectionTimeline(nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
+    ConnectionTimeline(std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline);
+
+    /**
+     * True if all contained timestamps are valid, false otherwise.
+     */
+    bool isComplete() const;
+    /**
+     * Set the dispatching-related times. Return true if the operation succeeded, false if the
+     * dispatching times have already been set. If this function returns false, it likely indicates
+     * an error from the app side.
+     */
+    bool setDispatchTimeline(nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
+    /**
+     * Set the graphics-related times. Return true if the operation succeeded, false if the
+     * graphics times have already been set. If this function returns false, it likely indicates
+     * an error from the app side.
+     */
+    bool setGraphicsTimeline(std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline);
+
+    inline bool operator==(const ConnectionTimeline& rhs) const;
+    inline bool operator!=(const ConnectionTimeline& rhs) const;
+
+private:
+    bool mHasDispatchTimeline = false;
+    bool mHasGraphicsTimeline = false;
+};
+
+struct InputEventTimeline {
+    InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime);
+    const bool isDown; // True if this is an ACTION_DOWN event
+    const nsecs_t eventTime;
+    const nsecs_t readTime;
+
+    struct IBinderHash {
+        std::size_t operator()(const sp<IBinder>& b) const {
+            return std::hash<IBinder*>{}(b.get());
+        }
+    };
+
+    std::unordered_map<sp<IBinder>, ConnectionTimeline, IBinderHash> connectionTimelines;
+
+    bool operator==(const InputEventTimeline& rhs) const;
+};
+
+class InputEventTimelineProcessor {
+protected:
+    InputEventTimelineProcessor() {}
+    virtual ~InputEventTimelineProcessor() {}
+
+public:
+    /**
+     * Process the provided timeline
+     */
+    virtual void processTimeline(const InputEventTimeline& timeline) = 0;
+};
+
+} // namespace inputdispatcher
+} // namespace android
+
+#endif // _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.cpp b/services/inputflinger/dispatcher/LatencyAggregator.cpp
new file mode 100644
index 0000000..a5bfc25
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyAggregator.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "LatencyAggregator"
+#include "LatencyAggregator.h"
+
+#include <inttypes.h>
+
+#include <android-base/stringprintf.h>
+#include <input/Input.h>
+#include <log/log.h>
+#include <server_configurable_flags/get_flags.h>
+
+using android::base::StringPrintf;
+using dist_proc::aggregation::KllQuantile;
+using std::chrono_literals::operator""ms;
+
+// Convert the provided nanoseconds into hundreds of microseconds.
+// Use hundreds of microseconds (as opposed to microseconds) to preserve space.
+static inline int64_t ns2hus(nsecs_t nanos) {
+    return ns2us(nanos) / 100;
+}
+
+// The maximum number of events that we will store in the statistics. Any events that we will
+// receive after we have reached this number will be ignored. We could also implement this by
+// checking the actual size of the current data and making sure that we do not go over. However,
+// the serialization process of sketches is too heavy (1 ms for all 14 sketches), and would be too
+// much to do (even if infrequently).
+// The value here has been determined empirically.
+static constexpr size_t MAX_EVENTS_FOR_STATISTICS = 20000;
+
+// Category (=namespace) name for the input settings that are applied at boot time
+static const char* INPUT_NATIVE_BOOT = "input_native_boot";
+// Feature flag name for the threshold of end-to-end touch latency that would trigger
+// SlowEventReported atom to be pushed
+static const char* SLOW_EVENT_MIN_REPORTING_LATENCY_MILLIS =
+        "slow_event_min_reporting_latency_millis";
+// Feature flag name for the minimum delay before reporting a slow event after having just reported
+// a slow event. This helps limit the amount of data sent to the server
+static const char* SLOW_EVENT_MIN_REPORTING_INTERVAL_MILLIS =
+        "slow_event_min_reporting_interval_millis";
+
+// If an event has end-to-end latency > 200 ms, it will get reported as a slow event.
+std::chrono::milliseconds DEFAULT_SLOW_EVENT_MIN_REPORTING_LATENCY = 200ms;
+// If we receive two slow events less than 1 min apart, we will only report 1 of them.
+std::chrono::milliseconds DEFAULT_SLOW_EVENT_MIN_REPORTING_INTERVAL = 60000ms;
+
+static std::chrono::milliseconds getSlowEventMinReportingLatency() {
+    std::string millis = server_configurable_flags::
+            GetServerConfigurableFlag(INPUT_NATIVE_BOOT, SLOW_EVENT_MIN_REPORTING_LATENCY_MILLIS,
+                                      std::to_string(
+                                              DEFAULT_SLOW_EVENT_MIN_REPORTING_LATENCY.count()));
+    return std::chrono::milliseconds(std::stoi(millis));
+}
+
+static std::chrono::milliseconds getSlowEventMinReportingInterval() {
+    std::string millis = server_configurable_flags::
+            GetServerConfigurableFlag(INPUT_NATIVE_BOOT, SLOW_EVENT_MIN_REPORTING_INTERVAL_MILLIS,
+                                      std::to_string(
+                                              DEFAULT_SLOW_EVENT_MIN_REPORTING_INTERVAL.count()));
+    return std::chrono::milliseconds(std::stoi(millis));
+}
+
+namespace android::inputdispatcher {
+
+/**
+ * Same as android::util::BytesField, but doesn't store raw pointers, and therefore deletes its
+ * resources automatically.
+ */
+class SafeBytesField {
+public:
+    explicit SafeBytesField(dist_proc::aggregation::KllQuantile& quantile) {
+        const zetasketch::android::AggregatorStateProto aggProto = quantile.SerializeToProto();
+        mBuffer.resize(aggProto.ByteSizeLong());
+        aggProto.SerializeToArray(mBuffer.data(), mBuffer.size());
+    }
+    android::util::BytesField getBytesField() {
+        return android::util::BytesField(mBuffer.data(), mBuffer.size());
+    }
+
+private:
+    std::vector<char> mBuffer;
+};
+
+LatencyAggregator::LatencyAggregator() {
+    AStatsManager_setPullAtomCallback(android::util::INPUT_EVENT_LATENCY_SKETCH, nullptr,
+                                      LatencyAggregator::pullAtomCallback, this);
+    dist_proc::aggregation::KllQuantileOptions options;
+    options.set_inv_eps(100); // Request precision of 1.0%, instead of default 0.1%
+    for (size_t i = 0; i < SketchIndex::SIZE; i++) {
+        mDownSketches[i] = KllQuantile::Create(options);
+        mMoveSketches[i] = KllQuantile::Create(options);
+    }
+}
+
+LatencyAggregator::~LatencyAggregator() {
+    AStatsManager_clearPullAtomCallback(android::util::INPUT_EVENT_LATENCY_SKETCH);
+}
+
+AStatsManager_PullAtomCallbackReturn LatencyAggregator::pullAtomCallback(int32_t atomTag,
+                                                                         AStatsEventList* data,
+                                                                         void* cookie) {
+    LatencyAggregator* pAggregator = reinterpret_cast<LatencyAggregator*>(cookie);
+    if (pAggregator == nullptr) {
+        LOG_ALWAYS_FATAL("pAggregator is null!");
+    }
+    return pAggregator->pullData(data);
+}
+
+void LatencyAggregator::processTimeline(const InputEventTimeline& timeline) {
+    processStatistics(timeline);
+    processSlowEvent(timeline);
+}
+
+void LatencyAggregator::processStatistics(const InputEventTimeline& timeline) {
+    // Before we do any processing, check that we have not yet exceeded MAX_SIZE
+    if (mNumSketchEventsProcessed >= MAX_EVENTS_FOR_STATISTICS) {
+        return;
+    }
+    mNumSketchEventsProcessed++;
+
+    std::array<std::unique_ptr<KllQuantile>, SketchIndex::SIZE>& sketches =
+            timeline.isDown ? mDownSketches : mMoveSketches;
+
+    // Process common ones first
+    const nsecs_t eventToRead = timeline.readTime - timeline.eventTime;
+    sketches[SketchIndex::EVENT_TO_READ]->Add(ns2hus(eventToRead));
+
+    // Now process per-connection ones
+    for (const auto& [connectionToken, connectionTimeline] : timeline.connectionTimelines) {
+        if (!connectionTimeline.isComplete()) {
+            continue;
+        }
+        const nsecs_t readToDeliver = connectionTimeline.deliveryTime - timeline.readTime;
+        const nsecs_t deliverToConsume =
+                connectionTimeline.consumeTime - connectionTimeline.deliveryTime;
+        const nsecs_t consumeToFinish =
+                connectionTimeline.finishTime - connectionTimeline.consumeTime;
+        const nsecs_t gpuCompletedTime =
+                connectionTimeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+        const nsecs_t presentTime =
+                connectionTimeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+        const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime;
+        const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime;
+        const nsecs_t endToEnd = presentTime - timeline.eventTime;
+
+        sketches[SketchIndex::READ_TO_DELIVER]->Add(ns2hus(readToDeliver));
+        sketches[SketchIndex::DELIVER_TO_CONSUME]->Add(ns2hus(deliverToConsume));
+        sketches[SketchIndex::CONSUME_TO_FINISH]->Add(ns2hus(consumeToFinish));
+        sketches[SketchIndex::CONSUME_TO_GPU_COMPLETE]->Add(ns2hus(consumeToGpuComplete));
+        sketches[SketchIndex::GPU_COMPLETE_TO_PRESENT]->Add(ns2hus(gpuCompleteToPresent));
+        sketches[SketchIndex::END_TO_END]->Add(ns2hus(endToEnd));
+    }
+}
+
+AStatsManager_PullAtomCallbackReturn LatencyAggregator::pullData(AStatsEventList* data) {
+    std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedDownData;
+    std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedMoveData;
+    for (size_t i = 0; i < SketchIndex::SIZE; i++) {
+        serializedDownData[i] = std::make_unique<SafeBytesField>(*mDownSketches[i]);
+        serializedMoveData[i] = std::make_unique<SafeBytesField>(*mMoveSketches[i]);
+    }
+    android::util::
+            addAStatsEvent(data, android::util::INPUT_EVENT_LATENCY_SKETCH,
+                           // DOWN sketches
+                           serializedDownData[SketchIndex::EVENT_TO_READ]->getBytesField(),
+                           serializedDownData[SketchIndex::READ_TO_DELIVER]->getBytesField(),
+                           serializedDownData[SketchIndex::DELIVER_TO_CONSUME]->getBytesField(),
+                           serializedDownData[SketchIndex::CONSUME_TO_FINISH]->getBytesField(),
+                           serializedDownData[SketchIndex::CONSUME_TO_GPU_COMPLETE]
+                                   ->getBytesField(),
+                           serializedDownData[SketchIndex::GPU_COMPLETE_TO_PRESENT]
+                                   ->getBytesField(),
+                           serializedDownData[SketchIndex::END_TO_END]->getBytesField(),
+                           // MOVE sketches
+                           serializedMoveData[SketchIndex::EVENT_TO_READ]->getBytesField(),
+                           serializedMoveData[SketchIndex::READ_TO_DELIVER]->getBytesField(),
+                           serializedMoveData[SketchIndex::DELIVER_TO_CONSUME]->getBytesField(),
+                           serializedMoveData[SketchIndex::CONSUME_TO_FINISH]->getBytesField(),
+                           serializedMoveData[SketchIndex::CONSUME_TO_GPU_COMPLETE]
+                                   ->getBytesField(),
+                           serializedMoveData[SketchIndex::GPU_COMPLETE_TO_PRESENT]
+                                   ->getBytesField(),
+                           serializedMoveData[SketchIndex::END_TO_END]->getBytesField());
+
+    for (size_t i = 0; i < SketchIndex::SIZE; i++) {
+        mDownSketches[i]->Reset();
+        mMoveSketches[i]->Reset();
+    }
+    // Start new aggregations
+    mNumSketchEventsProcessed = 0;
+    return AStatsManager_PULL_SUCCESS;
+}
+
+void LatencyAggregator::processSlowEvent(const InputEventTimeline& timeline) {
+    static const std::chrono::duration sSlowEventThreshold = getSlowEventMinReportingLatency();
+    static const std::chrono::duration sSlowEventReportingInterval =
+            getSlowEventMinReportingInterval();
+    for (const auto& [token, connectionTimeline] : timeline.connectionTimelines) {
+        if (!connectionTimeline.isComplete()) {
+            continue;
+        }
+        mNumEventsSinceLastSlowEventReport++;
+        const nsecs_t presentTime =
+                connectionTimeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+        const std::chrono::nanoseconds endToEndLatency =
+                std::chrono::nanoseconds(presentTime - timeline.eventTime);
+        if (endToEndLatency < sSlowEventThreshold) {
+            continue;
+        }
+        // This is a slow event. Before we report it, check if we are reporting too often
+        const std::chrono::duration elapsedSinceLastReport =
+                std::chrono::nanoseconds(timeline.eventTime - mLastSlowEventTime);
+        if (elapsedSinceLastReport < sSlowEventReportingInterval) {
+            mNumSkippedSlowEvents++;
+            continue;
+        }
+
+        const nsecs_t eventToRead = timeline.readTime - timeline.eventTime;
+        const nsecs_t readToDeliver = connectionTimeline.deliveryTime - timeline.readTime;
+        const nsecs_t deliverToConsume =
+                connectionTimeline.consumeTime - connectionTimeline.deliveryTime;
+        const nsecs_t consumeToFinish =
+                connectionTimeline.finishTime - connectionTimeline.consumeTime;
+        const nsecs_t gpuCompletedTime =
+                connectionTimeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+        const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime;
+        const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime;
+
+        android::util::stats_write(android::util::SLOW_INPUT_EVENT_REPORTED, timeline.isDown,
+                                   static_cast<int32_t>(ns2us(eventToRead)),
+                                   static_cast<int32_t>(ns2us(readToDeliver)),
+                                   static_cast<int32_t>(ns2us(deliverToConsume)),
+                                   static_cast<int32_t>(ns2us(consumeToFinish)),
+                                   static_cast<int32_t>(ns2us(consumeToGpuComplete)),
+                                   static_cast<int32_t>(ns2us(gpuCompleteToPresent)),
+                                   static_cast<int32_t>(ns2us(endToEndLatency.count())),
+                                   static_cast<int32_t>(mNumEventsSinceLastSlowEventReport),
+                                   static_cast<int32_t>(mNumSkippedSlowEvents));
+        mNumEventsSinceLastSlowEventReport = 0;
+        mNumSkippedSlowEvents = 0;
+        mLastSlowEventTime = timeline.readTime;
+    }
+}
+
+std::string LatencyAggregator::dump(const char* prefix) {
+    std::string sketchDump = StringPrintf("%s  Sketches:\n", prefix);
+    for (size_t i = 0; i < SketchIndex::SIZE; i++) {
+        const int64_t numDown = mDownSketches[i]->num_values();
+        SafeBytesField downBytesField(*mDownSketches[i]);
+        const float downBytesKb = downBytesField.getBytesField().arg_length * 1E-3;
+        const int64_t numMove = mMoveSketches[i]->num_values();
+        SafeBytesField moveBytesField(*mMoveSketches[i]);
+        const float moveBytesKb = moveBytesField.getBytesField().arg_length * 1E-3;
+        sketchDump +=
+                StringPrintf("%s    mDownSketches[%zu]->num_values = %" PRId64 " size = %.1fKB"
+                             " mMoveSketches[%zu]->num_values = %" PRId64 " size = %.1fKB\n",
+                             prefix, i, numDown, downBytesKb, i, numMove, moveBytesKb);
+    }
+
+    return StringPrintf("%sLatencyAggregator:\n", prefix) + sketchDump +
+            StringPrintf("%s  mNumSketchEventsProcessed=%zu\n", prefix, mNumSketchEventsProcessed) +
+            StringPrintf("%s  mLastSlowEventTime=%" PRId64 "\n", prefix, mLastSlowEventTime) +
+            StringPrintf("%s  mNumEventsSinceLastSlowEventReport = %zu\n", prefix,
+                         mNumEventsSinceLastSlowEventReport) +
+            StringPrintf("%s  mNumSkippedSlowEvents = %zu\n", prefix, mNumSkippedSlowEvents);
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.h b/services/inputflinger/dispatcher/LatencyAggregator.h
new file mode 100644
index 0000000..ed5731f
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyAggregator.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H
+#define _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H
+
+#include <kll.h>
+#include <statslog.h>
+#include <utils/Timers.h>
+
+#include "InputEventTimeline.h"
+
+namespace android::inputdispatcher {
+
+enum SketchIndex : size_t {
+    EVENT_TO_READ = 0,
+    READ_TO_DELIVER = 1,
+    DELIVER_TO_CONSUME = 2,
+    CONSUME_TO_FINISH = 3,
+    CONSUME_TO_GPU_COMPLETE = 4,
+    GPU_COMPLETE_TO_PRESENT = 5,
+    END_TO_END = 6, // EVENT_TO_PRESENT
+    SIZE = 7,       // Must be last
+};
+
+// Let's create a full timeline here:
+// eventTime
+// readTime
+// <---- after this point, the data becomes per-connection
+// deliveryTime // time at which the event was sent to the receiver
+// consumeTime  // time at which the receiver read the event
+// finishTime   // time at which the finish event was received
+// GraphicsTimeline::GPU_COMPLETED_TIME
+// GraphicsTimeline::PRESENT_TIME
+
+/**
+ * Keep sketches of the provided events and report slow events
+ */
+class LatencyAggregator final : public InputEventTimelineProcessor {
+public:
+    LatencyAggregator();
+    /**
+     * Record a complete event timeline
+     */
+    void processTimeline(const InputEventTimeline& timeline) override;
+
+    std::string dump(const char* prefix);
+
+    ~LatencyAggregator();
+
+private:
+    static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atom_tag,
+                                                                 AStatsEventList* data,
+                                                                 void* cookie);
+    AStatsManager_PullAtomCallbackReturn pullData(AStatsEventList* data);
+    // ---------- Slow event handling ----------
+    void processSlowEvent(const InputEventTimeline& timeline);
+    nsecs_t mLastSlowEventTime = 0;
+    // How many slow events have been skipped due to rate limiting
+    size_t mNumSkippedSlowEvents = 0;
+    // How many events have been received since the last time we reported a slow event
+    size_t mNumEventsSinceLastSlowEventReport = 0;
+
+    // ---------- Statistics handling ----------
+    void processStatistics(const InputEventTimeline& timeline);
+    // Sketches
+    std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
+            mDownSketches;
+    std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
+            mMoveSketches;
+    // How many events have been processed so far
+    size_t mNumSketchEventsProcessed = 0;
+};
+
+} // namespace android::inputdispatcher
+
+#endif // _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H
diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp
new file mode 100644
index 0000000..d634dcd
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyTracker.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "LatencyTracker"
+#include "LatencyTracker.h"
+
+#include <inttypes.h>
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android/os/IInputConstants.h>
+#include <input/Input.h>
+#include <log/log.h>
+
+using android::base::HwTimeoutMultiplier;
+using android::base::StringPrintf;
+
+namespace android::inputdispatcher {
+
+/**
+ * Events that are older than this time will be considered mature, at which point we will stop
+ * waiting for the apps to provide further information about them.
+ * It's likely that the apps will ANR if the events are not received by this deadline, and we
+ * already track ANR metrics separately.
+ */
+const std::chrono::duration ANR_TIMEOUT = std::chrono::milliseconds(
+        android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+        HwTimeoutMultiplier());
+
+static bool isMatureEvent(nsecs_t eventTime, nsecs_t now) {
+    std::chrono::duration age = std::chrono::nanoseconds(now) - std::chrono::nanoseconds(eventTime);
+    return age > ANR_TIMEOUT;
+}
+
+/**
+ * A multimap allows to have several entries with the same key. This function just erases a specific
+ * key-value pair. Equivalent to the imaginary std api std::multimap::erase(key, value).
+ */
+template <typename K, typename V>
+static void eraseByKeyAndValue(std::multimap<K, V>& map, K key, V value) {
+    auto iterpair = map.equal_range(key);
+
+    for (auto it = iterpair.first; it != iterpair.second; ++it) {
+        if (it->second == value) {
+            map.erase(it);
+            break;
+        }
+    }
+}
+
+LatencyTracker::LatencyTracker(InputEventTimelineProcessor* processor)
+      : mTimelineProcessor(processor) {
+    LOG_ALWAYS_FATAL_IF(processor == nullptr);
+}
+
+void LatencyTracker::trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime,
+                                   nsecs_t readTime) {
+    reportAndPruneMatureRecords(eventTime);
+    const auto it = mTimelines.find(inputEventId);
+    if (it != mTimelines.end()) {
+        // Input event ids are randomly generated, so it's possible that two events have the same
+        // event id. Drop this event, and also drop the existing event because the apps would
+        // confuse us by reporting the rest of the timeline for one of them. This should happen
+        // rarely, so we won't lose much data
+        mTimelines.erase(it);
+        // In case we have another input event with a different id and at the same eventTime,
+        // only erase this specific inputEventId.
+        eraseByKeyAndValue(mEventTimes, eventTime, inputEventId);
+        return;
+    }
+    mTimelines.emplace(inputEventId, InputEventTimeline(isDown, eventTime, readTime));
+    mEventTimes.emplace(eventTime, inputEventId);
+}
+
+void LatencyTracker::trackFinishedEvent(int32_t inputEventId, const sp<IBinder>& connectionToken,
+                                        nsecs_t deliveryTime, nsecs_t consumeTime,
+                                        nsecs_t finishTime) {
+    const auto it = mTimelines.find(inputEventId);
+    if (it == mTimelines.end()) {
+        // It's possible that an app sends a bad (or late)'Finish' signal, since it's free to do
+        // anything in its process. Just drop the report and move on.
+        return;
+    }
+
+    InputEventTimeline& timeline = it->second;
+    const auto connectionIt = timeline.connectionTimelines.find(connectionToken);
+    if (connectionIt == timeline.connectionTimelines.end()) {
+        // Most likely case: app calls 'finishInputEvent' before it reports the graphics timeline
+        timeline.connectionTimelines.emplace(connectionToken,
+                                             ConnectionTimeline{deliveryTime, consumeTime,
+                                                                finishTime});
+    } else {
+        // Already have a record for this connectionToken
+        ConnectionTimeline& connectionTimeline = connectionIt->second;
+        const bool success =
+                connectionTimeline.setDispatchTimeline(deliveryTime, consumeTime, finishTime);
+        if (!success) {
+            // We are receiving unreliable data from the app. Just delete the entire connection
+            // timeline for this event
+            timeline.connectionTimelines.erase(connectionIt);
+        }
+    }
+}
+
+void LatencyTracker::trackGraphicsLatency(
+        int32_t inputEventId, const sp<IBinder>& connectionToken,
+        std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
+    const auto it = mTimelines.find(inputEventId);
+    if (it == mTimelines.end()) {
+        // It's possible that an app sends a bad (or late) 'Timeline' signal, since it's free to do
+        // anything in its process. Just drop the report and move on.
+        return;
+    }
+
+    InputEventTimeline& timeline = it->second;
+    const auto connectionIt = timeline.connectionTimelines.find(connectionToken);
+    if (connectionIt == timeline.connectionTimelines.end()) {
+        timeline.connectionTimelines.emplace(connectionToken, std::move(graphicsTimeline));
+    } else {
+        // Most likely case
+        ConnectionTimeline& connectionTimeline = connectionIt->second;
+        const bool success = connectionTimeline.setGraphicsTimeline(std::move(graphicsTimeline));
+        if (!success) {
+            // We are receiving unreliable data from the app. Just delete the entire connection
+            // timeline for this event
+            timeline.connectionTimelines.erase(connectionIt);
+        }
+    }
+}
+
+/**
+ * We should use the current time 'now()' here to determine the age of the event, but instead we
+ * are using the latest 'eventTime' for efficiency since this time is already acquired, and
+ * 'trackListener' should happen soon after the event occurs.
+ */
+void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) {
+    while (!mEventTimes.empty()) {
+        const auto& [oldestEventTime, oldestInputEventId] = *mEventTimes.begin();
+        if (isMatureEvent(oldestEventTime, newEventTime /*now*/)) {
+            // Report and drop this event
+            const auto it = mTimelines.find(oldestInputEventId);
+            LOG_ALWAYS_FATAL_IF(it == mTimelines.end(),
+                                "Event %" PRId32 " is in mEventTimes, but not in mTimelines",
+                                oldestInputEventId);
+            const InputEventTimeline& timeline = it->second;
+            mTimelineProcessor->processTimeline(timeline);
+            mTimelines.erase(it);
+            mEventTimes.erase(mEventTimes.begin());
+        } else {
+            // If the oldest event does not need to be pruned, no events should be pruned.
+            return;
+        }
+    }
+}
+
+void LatencyTracker::reportNow() {
+    for (const auto& [inputEventId, timeline] : mTimelines) {
+        mTimelineProcessor->processTimeline(timeline);
+    }
+    mTimelines.clear();
+    mEventTimes.clear();
+}
+
+std::string LatencyTracker::dump(const char* prefix) {
+    return StringPrintf("%sLatencyTracker:\n", prefix) +
+            StringPrintf("%s  mTimelines.size() = %zu\n", prefix, mTimelines.size()) +
+            StringPrintf("%s  mEventTimes.size() = %zu\n", prefix, mEventTimes.size());
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyTracker.h b/services/inputflinger/dispatcher/LatencyTracker.h
new file mode 100644
index 0000000..289b8ed
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyTracker.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H
+#define _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H
+
+#include <map>
+#include <unordered_map>
+
+#include <binder/IBinder.h>
+#include <input/Input.h>
+
+#include "InputEventTimeline.h"
+
+namespace android::inputdispatcher {
+
+/**
+ * Maintain a record for input events that are received by InputDispatcher, sent out to the apps,
+ * and processed by the apps. Once an event becomes "mature" (older than the ANR timeout), report
+ * the entire input event latency history to the reporting function.
+ *
+ * All calls to LatencyTracker should come from the same thread. It is not thread-safe.
+ */
+class LatencyTracker {
+public:
+    /**
+     * Create a LatencyTracker.
+     * param reportingFunction: the function that will be called in order to report full latency.
+     */
+    LatencyTracker(InputEventTimelineProcessor* processor);
+    /**
+     * Start keeping track of an event identified by inputEventId. This must be called first.
+     */
+    void trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime, nsecs_t readTime);
+    void trackFinishedEvent(int32_t inputEventId, const sp<IBinder>& connectionToken,
+                            nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
+    void trackGraphicsLatency(int32_t inputEventId, const sp<IBinder>& connectionToken,
+                              std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
+
+    /**
+     * Report all collected events immediately, even if some of them are currently incomplete
+     * and may receive 'trackFinishedEvent' or 'trackGraphicsLatency' calls in the future.
+     * This is useful for tests. Otherwise, tests would have to inject additional "future" events,
+     * which is not convenient.
+     */
+    void reportNow();
+
+    std::string dump(const char* prefix);
+
+private:
+    /**
+     * A collection of InputEventTimelines keyed by inputEventId. An InputEventTimeline is first
+     * created when 'trackListener' is called.
+     * When either 'trackFinishedEvent' or 'trackGraphicsLatency' is called for this input event,
+     * the corresponding InputEventTimeline will be updated for that token.
+     */
+    std::unordered_map<int32_t /*inputEventId*/, InputEventTimeline> mTimelines;
+    /**
+     * The collection of eventTimes will help us quickly find the events that we should prune
+     * from the 'mTimelines'. Since 'mTimelines' is keyed by inputEventId, it would be inefficient
+     * to walk through it directly to find the oldest input events to get rid of.
+     * There is a 1:1 mapping between 'mTimelines' and 'mEventTimes'.
+     * We are using 'multimap' instead of 'map' because there could be more than 1 event with the
+     * same eventTime.
+     */
+    std::multimap<nsecs_t /*eventTime*/, int32_t /*inputEventId*/> mEventTimes;
+
+    InputEventTimelineProcessor* mTimelineProcessor;
+    void reportAndPruneMatureRecords(nsecs_t newEventTime);
+};
+
+} // namespace android::inputdispatcher
+
+#endif // _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 42b54c7..918e1be 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -46,6 +46,7 @@
         "InputDispatcher_test.cpp",
         "InputReader_test.cpp",
         "InputFlingerService_test.cpp",
+        "LatencyTracker_test.cpp",
         "TestInputListener.cpp",
         "UinputDevice.cpp",
     ],
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 855453e..93aa6ac 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -441,7 +441,7 @@
     sp<FakeInputDispatcherPolicy> mFakePolicy;
     sp<InputDispatcher> mDispatcher;
 
-    virtual void SetUp() override {
+    void SetUp() override {
         mFakePolicy = new FakeInputDispatcherPolicy();
         mDispatcher = new InputDispatcher(mFakePolicy);
         mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
@@ -449,7 +449,7 @@
         ASSERT_EQ(OK, mDispatcher->start());
     }
 
-    virtual void TearDown() override {
+    void TearDown() override {
         ASSERT_EQ(OK, mDispatcher->stop());
         mFakePolicy.clear();
         mDispatcher.clear();
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
new file mode 100644
index 0000000..e7e1937
--- /dev/null
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2021 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 "../dispatcher/LatencyTracker.h"
+
+#include <binder/Binder.h>
+#include <gtest/gtest.h>
+#include <inttypes.h>
+#include <log/log.h>
+
+#define TAG "LatencyTracker_test"
+
+using android::inputdispatcher::InputEventTimeline;
+using android::inputdispatcher::LatencyTracker;
+
+namespace android::inputdispatcher {
+
+InputEventTimeline getTestTimeline() {
+    InputEventTimeline t(
+            /*isDown*/ true,
+            /*eventTime*/ 2,
+            /*readTime*/ 3);
+    ConnectionTimeline expectedCT(/*deliveryTime*/ 6, /* consumeTime*/ 7, /*finishTime*/ 8);
+    std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+    graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 9;
+    graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 10;
+    expectedCT.setGraphicsTimeline(std::move(graphicsTimeline));
+    t.connectionTimelines.emplace(new BBinder(), std::move(expectedCT));
+    return t;
+}
+
+// --- LatencyTrackerTest ---
+class LatencyTrackerTest : public testing::Test, public InputEventTimelineProcessor {
+protected:
+    std::unique_ptr<LatencyTracker> mTracker;
+    sp<IBinder> connection1;
+    sp<IBinder> connection2;
+
+    void SetUp() override {
+        connection1 = new BBinder();
+        connection2 = new BBinder();
+
+        mTracker = std::make_unique<LatencyTracker>(this);
+    }
+    void TearDown() override {}
+
+    void assertReceivedTimeline(const InputEventTimeline& timeline);
+    /**
+     * Timelines can be received in any order (order is not guaranteed). So if we are expecting more
+     * than 1 timeline, use this function to check that the set of received timelines matches
+     * what we expected.
+     */
+    void assertReceivedTimelines(const std::vector<InputEventTimeline>& timelines);
+
+private:
+    void processTimeline(const InputEventTimeline& timeline) override {
+        mReceivedTimelines.push_back(timeline);
+    }
+    std::deque<InputEventTimeline> mReceivedTimelines;
+};
+
+void LatencyTrackerTest::assertReceivedTimeline(const InputEventTimeline& timeline) {
+    mTracker->reportNow();
+    ASSERT_FALSE(mReceivedTimelines.empty());
+    const InputEventTimeline& t = mReceivedTimelines.front();
+    ASSERT_EQ(timeline, t);
+    mReceivedTimelines.pop_front();
+}
+
+/**
+ * We are essentially comparing two multisets, but without constructing them.
+ * This comparison is inefficient, but it avoids having to construct a set, and also avoids the
+ * declaration of copy constructor for ConnectionTimeline.
+ * We ensure that collections A and B have the same size, that for every element in A, there is an
+ * equal element in B, and for every element in B there is an equal element in A.
+ */
+void LatencyTrackerTest::assertReceivedTimelines(const std::vector<InputEventTimeline>& timelines) {
+    mTracker->reportNow();
+    ASSERT_EQ(timelines.size(), mReceivedTimelines.size());
+    for (const InputEventTimeline& expectedTimeline : timelines) {
+        bool found = false;
+        for (const InputEventTimeline& receivedTimeline : mReceivedTimelines) {
+            if (receivedTimeline == expectedTimeline) {
+                found = true;
+                break;
+            }
+        }
+        ASSERT_TRUE(found) << "Could not find expected timeline with eventTime="
+                           << expectedTimeline.eventTime;
+    }
+    for (const InputEventTimeline& receivedTimeline : mReceivedTimelines) {
+        bool found = false;
+        for (const InputEventTimeline& expectedTimeline : timelines) {
+            if (receivedTimeline == expectedTimeline) {
+                found = true;
+                break;
+            }
+        }
+        ASSERT_TRUE(found) << "Could not find received timeline with eventTime="
+                           << receivedTimeline.eventTime;
+    }
+    mReceivedTimelines.clear();
+}
+
+/**
+ * Ensure that calling 'trackListener' in isolation only creates an inputflinger timeline, without
+ * any additional ConnectionTimeline's.
+ */
+TEST_F(LatencyTrackerTest, TrackListener_DoesNotTriggerReporting) {
+    mTracker->trackListener(1 /*inputEventId*/, false /*isDown*/, 2 /*eventTime*/, 3 /*readTime*/);
+    assertReceivedTimeline(InputEventTimeline{false, 2, 3});
+}
+
+/**
+ * A single call to trackFinishedEvent should not cause a timeline to be reported.
+ */
+TEST_F(LatencyTrackerTest, TrackFinishedEvent_DoesNotTriggerReporting) {
+    mTracker->trackFinishedEvent(1 /*inputEventId*/, connection1, 2 /*deliveryTime*/,
+                                 3 /*consumeTime*/, 4 /*finishTime*/);
+    assertReceivedTimelines({});
+}
+
+/**
+ * A single call to trackGraphicsLatency should not cause a timeline to be reported.
+ */
+TEST_F(LatencyTrackerTest, TrackGraphicsLatency_DoesNotTriggerReporting) {
+    std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+    graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
+    graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3;
+    mTracker->trackGraphicsLatency(1 /*inputEventId*/, connection2, graphicsTimeline);
+    assertReceivedTimelines({});
+}
+
+TEST_F(LatencyTrackerTest, TrackAllParameters_ReportsFullTimeline) {
+    constexpr int32_t inputEventId = 1;
+    InputEventTimeline expected = getTestTimeline();
+
+    const auto& [connectionToken, expectedCT] = *expected.connectionTimelines.begin();
+
+    mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime);
+    mTracker->trackFinishedEvent(inputEventId, connectionToken, expectedCT.deliveryTime,
+                                 expectedCT.consumeTime, expectedCT.finishTime);
+    mTracker->trackGraphicsLatency(inputEventId, connectionToken, expectedCT.graphicsTimeline);
+
+    assertReceivedTimeline(expected);
+}
+
+TEST_F(LatencyTrackerTest, MultipleEvents_AreReportedConsistently) {
+    constexpr int32_t inputEventId1 = 1;
+    InputEventTimeline timeline1(
+            /*isDown*/ true,
+            /*eventTime*/ 2,
+            /*readTime*/ 3);
+    timeline1.connectionTimelines.emplace(connection1,
+                                          ConnectionTimeline(/*deliveryTime*/ 6, /*consumeTime*/ 7,
+                                                             /*finishTime*/ 8));
+    ConnectionTimeline& connectionTimeline1 = timeline1.connectionTimelines.begin()->second;
+    std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline1;
+    graphicsTimeline1[GraphicsTimeline::GPU_COMPLETED_TIME] = 9;
+    graphicsTimeline1[GraphicsTimeline::PRESENT_TIME] = 10;
+    connectionTimeline1.setGraphicsTimeline(std::move(graphicsTimeline1));
+
+    constexpr int32_t inputEventId2 = 10;
+    InputEventTimeline timeline2(
+            /*isDown*/ false,
+            /*eventTime*/ 20,
+            /*readTime*/ 30);
+    timeline2.connectionTimelines.emplace(connection2,
+                                          ConnectionTimeline(/*deliveryTime*/ 60,
+                                                             /*consumeTime*/ 70,
+                                                             /*finishTime*/ 80));
+    ConnectionTimeline& connectionTimeline2 = timeline2.connectionTimelines.begin()->second;
+    std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline2;
+    graphicsTimeline2[GraphicsTimeline::GPU_COMPLETED_TIME] = 90;
+    graphicsTimeline2[GraphicsTimeline::PRESENT_TIME] = 100;
+    connectionTimeline2.setGraphicsTimeline(std::move(graphicsTimeline2));
+
+    // Start processing first event
+    mTracker->trackListener(inputEventId1, timeline1.isDown, timeline1.eventTime,
+                            timeline1.readTime);
+    // Start processing second event
+    mTracker->trackListener(inputEventId2, timeline2.isDown, timeline2.eventTime,
+                            timeline2.readTime);
+    mTracker->trackFinishedEvent(inputEventId1, connection1, connectionTimeline1.deliveryTime,
+                                 connectionTimeline1.consumeTime, connectionTimeline1.finishTime);
+
+    mTracker->trackFinishedEvent(inputEventId2, connection2, connectionTimeline2.deliveryTime,
+                                 connectionTimeline2.consumeTime, connectionTimeline2.finishTime);
+    mTracker->trackGraphicsLatency(inputEventId1, connection1,
+                                   connectionTimeline1.graphicsTimeline);
+    mTracker->trackGraphicsLatency(inputEventId2, connection2,
+                                   connectionTimeline2.graphicsTimeline);
+    // Now both events should be completed
+    assertReceivedTimelines({timeline1, timeline2});
+}
+
+/**
+ * Check that LatencyTracker consistently tracks events even if there are many incomplete events.
+ */
+TEST_F(LatencyTrackerTest, IncompleteEvents_AreHandledConsistently) {
+    InputEventTimeline timeline = getTestTimeline();
+    std::vector<InputEventTimeline> expectedTimelines;
+    const ConnectionTimeline& expectedCT = timeline.connectionTimelines.begin()->second;
+    const sp<IBinder>& token = timeline.connectionTimelines.begin()->first;
+
+    for (size_t i = 1; i <= 100; i++) {
+        mTracker->trackListener(i /*inputEventId*/, timeline.isDown, timeline.eventTime,
+                                timeline.readTime);
+        expectedTimelines.push_back(
+                InputEventTimeline{timeline.isDown, timeline.eventTime, timeline.readTime});
+    }
+    // Now, complete the first event that was sent.
+    mTracker->trackFinishedEvent(1 /*inputEventId*/, token, expectedCT.deliveryTime,
+                                 expectedCT.consumeTime, expectedCT.finishTime);
+    mTracker->trackGraphicsLatency(1 /*inputEventId*/, token, expectedCT.graphicsTimeline);
+
+    expectedTimelines[0].connectionTimelines.emplace(token, std::move(expectedCT));
+    assertReceivedTimelines(expectedTimelines);
+}
+
+/**
+ * For simplicity of the implementation, LatencyTracker only starts tracking an event when
+ * 'trackListener' is invoked.
+ * Both 'trackFinishedEvent' and 'trackGraphicsLatency' should not start a new event.
+ * If they are received before 'trackListener' (which should not be possible), they are ignored.
+ */
+TEST_F(LatencyTrackerTest, EventsAreTracked_WhenTrackListenerIsCalledFirst) {
+    constexpr int32_t inputEventId = 1;
+    InputEventTimeline expected = getTestTimeline();
+    const ConnectionTimeline& expectedCT = expected.connectionTimelines.begin()->second;
+    mTracker->trackFinishedEvent(inputEventId, connection1, expectedCT.deliveryTime,
+                                 expectedCT.consumeTime, expectedCT.finishTime);
+    mTracker->trackGraphicsLatency(inputEventId, connection1, expectedCT.graphicsTimeline);
+
+    mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime);
+    assertReceivedTimeline(
+            InputEventTimeline{expected.isDown, expected.eventTime, expected.readTime});
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index dc6eab4..fdcd6ab 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -78,6 +78,7 @@
     size_t getDisplayCost() const;
 
     bool hasBufferUpdate() const;
+    bool hasRenderedBuffer() const { return mTexture != nullptr; }
     bool hasReadyBuffer() const;
 
     // Decomposes this CachedSet into a vector of its layers as individual CachedSets
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 294ec4b..fdd73af 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -91,7 +91,7 @@
 
 void Flattener::renderCachedSets(renderengine::RenderEngine& renderEngine,
                                  const OutputCompositionState& outputState) {
-    if (!mNewCachedSet) {
+    if (!mNewCachedSet || mNewCachedSet->hasRenderedBuffer()) {
         return;
     }
 
@@ -393,6 +393,11 @@
         return;
     }
 
+    // Don't try to build a new cached set if we already have a new one in progress
+    if (mNewCachedSet) {
+        return;
+    }
+
     std::vector<Run> runs = findCandidateRuns(now);
 
     std::optional<Run> bestRun = findBestRun(runs);
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index 488f64d..8f44677 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -134,6 +134,7 @@
     EXPECT_NE(nullptr, cachedSet.getBuffer());
     EXPECT_NE(nullptr, cachedSet.getDrawFence());
     EXPECT_TRUE(cachedSet.hasReadyBuffer());
+    EXPECT_TRUE(cachedSet.hasRenderedBuffer());
 }
 
 TEST_F(CachedSetTest, createFromLayer) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 42096f3..7ec2c98 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -786,5 +786,45 @@
     EXPECT_EQ(overrideBuffer3, overrideBuffer4);
 }
 
+TEST_F(FlattenerTest, flattenLayers_renderCachedSets_doesNotRenderTwice) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+    auto& layerState2 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+    };
+
+    initializeFlattener(layers);
+
+    // Mark the layers inactive
+    mTime += 200ms;
+    // layers would be flattened but the buffer would not be overridden
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+
+    // Simulate attempting to render prior to merging the new cached set with the layer stack.
+    // Here we should not try to re-render.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    // We provide the override buffer now that it's rendered
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer2, overrideBuffer1);
+}
+
 } // namespace
 } // namespace android::compositionengine