Merge "Fix caching runtime toggle" into sc-dev
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 8ac4ff8..25e6dc9 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1696,6 +1696,12 @@
 
     RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunDumpsysHigh);
 
+    // The dump mechanism in connectivity is refactored due to modularization work. Connectivity can
+    // only register with a default priority(NORMAL priority). Dumpstate has to call connectivity
+    // dump with priority parameters to dump high priority information.
+    RunDumpsys("SERVICE HIGH connectivity", {"connectivity", "--dump-priority", "HIGH"},
+                   CommandOptions::WithTimeout(10).Build());
+
     RunCommand("SYSTEM PROPERTIES", {"getprop"});
 
     RunCommand("STORAGED IO INFO", {"storaged", "-u", "-p"});
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/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 18b77e6..c605e67 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -366,45 +366,19 @@
 
 pid_t IPCThreadState::getCallingPid() const
 {
-    checkContextIsBinderForUse(__func__);
     return mCallingPid;
 }
 
 const char* IPCThreadState::getCallingSid() const
 {
-    checkContextIsBinderForUse(__func__);
     return mCallingSid;
 }
 
 uid_t IPCThreadState::getCallingUid() const
 {
-    checkContextIsBinderForUse(__func__);
     return mCallingUid;
 }
 
-IPCThreadState::SpGuard* IPCThreadState::pushGetCallingSpGuard(SpGuard* guard) {
-    SpGuard* orig = mServingStackPointerGuard;
-    mServingStackPointerGuard = guard;
-    return orig;
-}
-
-void IPCThreadState::restoreGetCallingSpGuard(SpGuard* guard) {
-    mServingStackPointerGuard = guard;
-}
-
-void IPCThreadState::checkContextIsBinderForUse(const char* use) const {
-    if (mServingStackPointerGuard == nullptr) return;
-
-    if (!mServingStackPointer || mServingStackPointerGuard < mServingStackPointer) {
-        LOG_ALWAYS_FATAL("In context %s, %s does not make sense.",
-                         mServingStackPointerGuard->context, use);
-    }
-
-    // in the case mServingStackPointer is deeper in the stack than the guard,
-    // we must be serving a binder transaction (maybe nested). This is a binder
-    // context, so we don't abort
-}
-
 int64_t IPCThreadState::clearCallingIdentity()
 {
     // ignore mCallingSid for legacy reasons
@@ -515,14 +489,16 @@
 
 bool IPCThreadState::flushIfNeeded()
 {
-    if (mIsLooper || mServingStackPointer != nullptr) {
+    if (mIsLooper || mServingStackPointer != nullptr || mIsFlushing) {
         return false;
     }
+    mIsFlushing = true;
     // In case this thread is not a looper and is not currently serving a binder transaction,
     // there's no guarantee that this thread will call back into the kernel driver any time
     // soon. Therefore, flush pending commands such as BC_FREE_BUFFER, to prevent them from getting
     // stuck in this thread's out buffer.
     flushCommands();
+    mIsFlushing = false;
     return true;
 }
 
@@ -875,10 +851,10 @@
 IPCThreadState::IPCThreadState()
       : mProcess(ProcessState::self()),
         mServingStackPointer(nullptr),
-        mServingStackPointerGuard(nullptr),
         mWorkSource(kUnsetWorkSource),
         mPropagateWorkSource(false),
         mIsLooper(false),
+        mIsFlushing(false),
         mStrictModePolicy(0),
         mLastTransactionBinderFlags(0),
         mCallRestriction(mProcess->mCallRestriction) {
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index e5a6026..2ba9fa2 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -18,9 +18,7 @@
 
 #include "RpcState.h"
 
-#include <android-base/scopeguard.h>
 #include <binder/BpBinder.h>
-#include <binder/IPCThreadState.h>
 #include <binder/RpcServer.h>
 
 #include "Debug.h"
@@ -30,8 +28,6 @@
 
 namespace android {
 
-using base::ScopeGuard;
-
 RpcState::RpcState() {}
 RpcState::~RpcState() {}
 
@@ -474,18 +470,6 @@
 
 status_t RpcState::processServerCommand(const base::unique_fd& fd, const sp<RpcSession>& session,
                                         const RpcWireHeader& command) {
-    IPCThreadState* kernelBinderState = IPCThreadState::selfOrNull();
-    IPCThreadState::SpGuard spGuard{"processing binder RPC command"};
-    IPCThreadState::SpGuard* origGuard;
-    if (kernelBinderState != nullptr) {
-        origGuard = kernelBinderState->pushGetCallingSpGuard(&spGuard);
-    }
-    ScopeGuard guardUnguard = [&]() {
-        if (kernelBinderState != nullptr) {
-            kernelBinderState->restoreGetCallingSpGuard(origGuard);
-        }
-    };
-
     switch (command.command) {
         case RPC_COMMAND_TRANSACT:
             return processTransact(fd, session, command);
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 5220b62..5d04ebe 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -81,32 +81,6 @@
              */
             uid_t               getCallingUid() const;
 
-            /**
-             * Make it an abort to rely on getCalling* for a section of
-             * execution.
-             *
-             * Usage:
-             *     IPCThreadState::SpGuard guard { "..." };
-             *     auto* orig = pushGetCallingSpGuard(&guard);
-             *     {
-             *         // will abort if you call getCalling*, unless you are
-             *         // serving a nested binder transaction
-             *     }
-             *     restoreCallingSpGuard(orig);
-             */
-            struct SpGuard {
-                const char* context;
-            };
-            SpGuard* pushGetCallingSpGuard(SpGuard* guard);
-            void restoreGetCallingSpGuard(SpGuard* guard);
-            /**
-             * Used internally by getCalling*. Can also be used to assert that
-             * you are in a binder context (getCalling* is valid). This is
-             * intentionally not exposed as a boolean API since code should be
-             * written to know its environment.
-             */
-            void checkContextIsBinderForUse(const char* use) const;
-
             void                setStrictModePolicy(int32_t policy);
             int32_t             getStrictModePolicy() const;
 
@@ -229,7 +203,6 @@
             Parcel              mOut;
             status_t            mLastError;
             const void*         mServingStackPointer;
-            SpGuard* mServingStackPointerGuard;
             pid_t               mCallingPid;
             const char*         mCallingSid;
             uid_t               mCallingUid;
@@ -239,6 +212,7 @@
             // Whether the work source should be propagated.
             bool                mPropagateWorkSource;
             bool                mIsLooper;
+            bool mIsFlushing;
             int32_t             mStrictModePolicy;
             int32_t             mLastTransactionBinderFlags;
             CallRestriction     mCallRestriction;
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 9e2050b..78f2d3a 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -150,6 +150,11 @@
 /**
  * This is called whenever a transaction needs to be processed by a local implementation.
  *
+ * This method will be called after the equivalent of
+ * android.os.Parcel#enforceInterface is called. That is, the interface
+ * descriptor associated with the AIBinder_Class descriptor will already be
+ * checked.
+ *
  * \param binder the object being transacted on.
  * \param code implementation-specific code representing which transaction should be taken.
  * \param in the implementation-specific input data to this transaction.
@@ -452,12 +457,14 @@
  */
 
 /**
- * Creates a parcel to start filling out for a transaction. This may add data to the parcel for
- * security, debugging, or other purposes. This parcel is to be sent via AIBinder_transact and it
- * represents the input data to the transaction. It is recommended to check if the object is local
- * and call directly into its user data before calling this as the parceling and unparceling cost
- * can be avoided. This AIBinder must be either built with a class or associated with a class before
- * using this API.
+ * Creates a parcel to start filling out for a transaction. This will add a header to the
+ * transaction that corresponds to android.os.Parcel#writeInterfaceToken. This may add debugging
+ * or other information to the transaction for platform use or to enable other features to work. The
+ * contents of this header is a platform implementation detail, and it is required to use
+ * libbinder_ndk. This parcel is to be sent via AIBinder_transact and it represents the input data
+ * to the transaction. It is recommended to check if the object is local and call directly into its
+ * user data before calling this as the parceling and unparceling cost can be avoided. This AIBinder
+ * must be either built with a class or associated with a class before using this API.
  *
  * This does not affect the ownership of binder. When this function succeeds, the in parcel's
  * ownership is passed to the caller. At this point, the parcel can be filled out and passed to
diff --git a/libs/binder/tests/IBinderRpcTest.aidl b/libs/binder/tests/IBinderRpcTest.aidl
index 41daccc..ef4198d 100644
--- a/libs/binder/tests/IBinderRpcTest.aidl
+++ b/libs/binder/tests/IBinderRpcTest.aidl
@@ -55,6 +55,4 @@
     oneway void sleepMsAsync(int ms);
 
     void die(boolean cleanup);
-
-    void useKernelBinderCallingId();
 }
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 45b2776..0c3fbcd 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -73,7 +73,6 @@
     BINDER_LIB_TEST_REGISTER_SERVER,
     BINDER_LIB_TEST_ADD_SERVER,
     BINDER_LIB_TEST_ADD_POLL_SERVER,
-    BINDER_LIB_TEST_USE_CALLING_GUARD_TRANSACTION,
     BINDER_LIB_TEST_CALL_BACK,
     BINDER_LIB_TEST_CALL_BACK_VERIFY_BUF,
     BINDER_LIB_TEST_DELAYED_CALL_BACK,
@@ -605,24 +604,6 @@
     EXPECT_THAT(callBack->getResult(), StatusEq(NO_ERROR));
 }
 
-TEST_F(BinderLibTest, NoBinderCallContextGuard) {
-    IPCThreadState::SpGuard spGuard{"NoBinderCallContext"};
-    IPCThreadState::SpGuard *origGuard = IPCThreadState::self()->pushGetCallingSpGuard(&spGuard);
-
-    // yes, this test uses threads, but it's careful and uses fork in addServer
-    EXPECT_DEATH({ IPCThreadState::self()->getCallingPid(); },
-                 "In context NoBinderCallContext, getCallingPid does not make sense.");
-
-    IPCThreadState::self()->restoreGetCallingSpGuard(origGuard);
-}
-
-TEST_F(BinderLibTest, BinderCallContextGuard) {
-    sp<IBinder> binder = addServer();
-    Parcel data, reply;
-    EXPECT_THAT(binder->transact(BINDER_LIB_TEST_USE_CALLING_GUARD_TRANSACTION, data, &reply),
-                StatusEq(DEAD_OBJECT));
-}
-
 TEST_F(BinderLibTest, AddServer)
 {
     sp<IBinder> server = addServer();
@@ -1281,18 +1262,6 @@
                 pthread_mutex_unlock(&m_serverWaitMutex);
                 return ret;
             }
-            case BINDER_LIB_TEST_USE_CALLING_GUARD_TRANSACTION: {
-                IPCThreadState::SpGuard spGuard{"GuardInBinderTransaction"};
-                IPCThreadState::SpGuard *origGuard =
-                        IPCThreadState::self()->pushGetCallingSpGuard(&spGuard);
-
-                // if the guard works, this should abort
-                (void)IPCThreadState::self()->getCallingPid();
-
-                IPCThreadState::self()->restoreGetCallingSpGuard(origGuard);
-                return NO_ERROR;
-            }
-
             case BINDER_LIB_TEST_GETPID:
                 reply->writeInt32(getpid());
                 return NO_ERROR;
@@ -1520,11 +1489,6 @@
 {
     binderLibTestServiceName += String16(binderserversuffix);
 
-    // Testing to make sure that calls that we are serving can use getCallin*
-    // even though we don't here.
-    IPCThreadState::SpGuard spGuard{"main server thread"};
-    (void)IPCThreadState::self()->pushGetCallingSpGuard(&spGuard);
-
     status_t ret;
     sp<IServiceManager> sm = defaultServiceManager();
     BinderLibTestService* testServicePtr;
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 3f94df2..a96deb5 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -23,7 +23,6 @@
 #include <android/binder_libbinder.h>
 #include <binder/Binder.h>
 #include <binder/BpBinder.h>
-#include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
 #include <binder/RpcServer.h>
@@ -192,13 +191,6 @@
             _exit(1);
         }
     }
-    Status useKernelBinderCallingId() override {
-        // this is WRONG! It does not make sense when using RPC binder, and
-        // because it is SO wrong, and so much code calls this, it should abort!
-
-        (void)IPCThreadState::self()->getCallingPid();
-        return Status::ok();
-    }
 };
 sp<IBinder> MyBinderRpcTest::mHeldBinder;
 
@@ -895,19 +887,6 @@
     }
 }
 
-TEST_P(BinderRpc, UseKernelBinderCallingId) {
-    auto proc = createRpcTestSocketServerProcess(1);
-
-    // we can't allocate IPCThreadState so actually the first time should
-    // succeed :(
-    EXPECT_OK(proc.rootIface->useKernelBinderCallingId());
-
-    // second time! we catch the error :)
-    EXPECT_EQ(DEAD_OBJECT, proc.rootIface->useKernelBinderCallingId().transactionError());
-
-    proc.expectInvalid = true;
-}
-
 TEST_P(BinderRpc, WorksWithLibbinderNdkPing) {
     auto proc = createRpcTestSocketServerProcess(1);
 
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 08800f7..a2868c6 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -317,6 +317,11 @@
     std::unique_lock _lock{mMutex};
     BQA_LOGV("releaseBufferCallback graphicBufferId=%" PRIu64, graphicBufferId);
 
+    if (mSurfaceControl != nullptr) {
+        mTransformHint = mSurfaceControl->getTransformHint();
+        mBufferItemConsumer->setTransformHint(mTransformHint);
+    }
+
     auto it = mSubmitted.find(graphicBufferId);
     if (it == mSubmitted.end()) {
         BQA_LOGE("ERROR: releaseBufferCallback without corresponding submitted buffer %" PRIu64,
@@ -596,6 +601,14 @@
     status_t setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) override {
         return mBbq->setFrameTimelineInfo(frameTimelineInfo);
     }
+ protected:
+    uint32_t getTransformHint() const override {
+        if (mStickyTransform == 0 && !transformToDisplayInverse()) {
+            return mBbq->getLastTransformHint();
+        } else {
+            return 0;
+        }
+    }
 };
 
 // TODO: Can we coalesce this with frame updates? Need to confirm
@@ -765,4 +778,12 @@
     return convertedFormat;
 }
 
+uint32_t BLASTBufferQueue::getLastTransformHint() const {
+    if (mSurfaceControl != nullptr) {
+        return mSurfaceControl->getTransformHint();
+    } else {
+        return 0;
+    }
+}
+
 } // namespace android
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index a7cf39a..df308d8 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -1621,6 +1621,26 @@
     return NO_ERROR;
 }
 
+status_t BufferQueueProducer::getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence,
+                                                  Rect* outRect, uint32_t* outTransform) {
+    ATRACE_CALL();
+    BQ_LOGV("getLastQueuedBuffer");
+
+    std::lock_guard<std::mutex> lock(mCore->mMutex);
+    if (mCore->mLastQueuedSlot == BufferItem::INVALID_BUFFER_SLOT) {
+        *outBuffer = nullptr;
+        *outFence = Fence::NO_FENCE;
+        return NO_ERROR;
+    }
+
+    *outBuffer = mSlots[mCore->mLastQueuedSlot].mGraphicBuffer;
+    *outFence = mLastQueueBufferFence;
+    *outRect = mLastQueuedCrop;
+    *outTransform = mLastQueuedTransform;
+
+    return NO_ERROR;
+}
+
 void BufferQueueProducer::getFrameTimestamps(FrameEventHistoryDelta* outDelta) {
     addAndGetFrameTimestamps(nullptr, outDelta);
 }
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index c1f9b85..797069c 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -81,6 +81,7 @@
     QUEUE_BUFFERS,
     CANCEL_BUFFERS,
     QUERY_MULTIPLE,
+    GET_LAST_QUEUED_BUFFER2,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -646,6 +647,56 @@
         return result;
     }
 
+    virtual status_t getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence,
+                                         Rect* outRect, uint32_t* outTransform) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        status_t result = remote()->transact(GET_LAST_QUEUED_BUFFER2, data, &reply);
+        if (result != NO_ERROR) {
+            ALOGE("getLastQueuedBuffer failed to transact: %d", result);
+            return result;
+        }
+        status_t remoteError = NO_ERROR;
+        result = reply.readInt32(&remoteError);
+        if (result != NO_ERROR) {
+            ALOGE("getLastQueuedBuffer failed to read status: %d", result);
+            return result;
+        }
+        if (remoteError != NO_ERROR) {
+            return remoteError;
+        }
+        bool hasBuffer = false;
+        result = reply.readBool(&hasBuffer);
+        if (result != NO_ERROR) {
+            ALOGE("getLastQueuedBuffer failed to read buffer: %d", result);
+            return result;
+        }
+        sp<GraphicBuffer> buffer;
+        if (hasBuffer) {
+            buffer = new GraphicBuffer();
+            result = reply.read(*buffer);
+            if (result == NO_ERROR) {
+                result = reply.read(*outRect);
+            }
+            if (result == NO_ERROR) {
+                result = reply.readUint32(outTransform);
+            }
+        }
+        if (result != NO_ERROR) {
+            ALOGE("getLastQueuedBuffer failed to read buffer: %d", result);
+            return result;
+        }
+        sp<Fence> fence(new Fence);
+        result = reply.read(*fence);
+        if (result != NO_ERROR) {
+            ALOGE("getLastQueuedBuffer failed to read fence: %d", result);
+            return result;
+        }
+        *outBuffer = buffer;
+        *outFence = fence;
+        return result;
+    }
+
     virtual void getFrameTimestamps(FrameEventHistoryDelta* outDelta) {
         Parcel data, reply;
         status_t result = data.writeInterfaceToken(
@@ -870,6 +921,11 @@
                 outBuffer, outFence, outTransformMatrix);
     }
 
+    status_t getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence, Rect* outRect,
+                                 uint32_t* outTransform) override {
+        return mBase->getLastQueuedBuffer(outBuffer, outFence, outRect, outTransform);
+    }
+
     void getFrameTimestamps(FrameEventHistoryDelta* outDelta) override {
         return mBase->getFrameTimestamps(outDelta);
     }
@@ -1362,6 +1418,45 @@
             }
             return NO_ERROR;
         }
+        case GET_LAST_QUEUED_BUFFER2: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            sp<GraphicBuffer> buffer(nullptr);
+            sp<Fence> fence(Fence::NO_FENCE);
+            Rect crop;
+            uint32_t transform;
+            status_t result = getLastQueuedBuffer(&buffer, &fence, &crop, &transform);
+            reply->writeInt32(result);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            if (!buffer.get()) {
+                reply->writeBool(false);
+            } else {
+                reply->writeBool(true);
+                result = reply->write(*buffer);
+                if (result == NO_ERROR) {
+                    result = reply->write(crop);
+                }
+                if (result == NO_ERROR) {
+                    result = reply->writeUint32(transform);
+                }
+            }
+            if (result != NO_ERROR) {
+                ALOGE("getLastQueuedBuffer failed to write buffer: %d", result);
+                return result;
+            }
+            if (fence == nullptr) {
+                ALOGE("getLastQueuedBuffer returned a NULL fence, setting to Fence::NO_FENCE");
+                fence = Fence::NO_FENCE;
+            }
+            result = reply->write(*fence);
+            if (result != NO_ERROR) {
+                ALOGE("getLastQueuedBuffer failed to write fence: %d", result);
+                return result;
+            }
+            return NO_ERROR;
+        }
+
         case GET_FRAME_TIMESTAMPS: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
             FrameEventHistoryDelta frameTimestamps;
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 2fc9d47..d27d1ec 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1288,7 +1288,7 @@
                         mUserHeight ? mUserHeight : mDefaultHeight);
                 return NO_ERROR;
             case NATIVE_WINDOW_TRANSFORM_HINT:
-                *value = static_cast<int>(mTransformHint);
+                *value = static_cast<int>(getTransformHint());
                 return NO_ERROR;
             case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND: {
                 status_t err = NO_ERROR;
@@ -1492,6 +1492,9 @@
     case NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER:
         res = dispatchGetLastQueuedBuffer(args);
         break;
+    case NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2:
+        res = dispatchGetLastQueuedBuffer2(args);
+        break;
     case NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO:
         res = dispatchSetFrameTimelineInfo(args);
         break;
@@ -1805,6 +1808,39 @@
     return result;
 }
 
+int Surface::dispatchGetLastQueuedBuffer2(va_list args) {
+    AHardwareBuffer** buffer = va_arg(args, AHardwareBuffer**);
+    int* fence = va_arg(args, int*);
+    ARect* crop = va_arg(args, ARect*);
+    uint32_t* transform = va_arg(args, uint32_t*);
+    sp<GraphicBuffer> graphicBuffer;
+    sp<Fence> spFence;
+
+    Rect r;
+    int result =
+            mGraphicBufferProducer->getLastQueuedBuffer(&graphicBuffer, &spFence, &r, transform);
+
+    if (graphicBuffer != nullptr) {
+        *buffer = graphicBuffer->toAHardwareBuffer();
+        AHardwareBuffer_acquire(*buffer);
+
+        // Avoid setting crop* unless buffer is valid (matches IGBP behavior)
+        crop->left = r.left;
+        crop->top = r.top;
+        crop->right = r.right;
+        crop->bottom = r.bottom;
+    } else {
+        *buffer = nullptr;
+    }
+
+    if (spFence != nullptr) {
+        *fence = spFence->dup();
+    } else {
+        *fence = -1;
+    }
+    return result;
+}
+
 int Surface::dispatchSetFrameTimelineInfo(va_list args) {
     ATRACE_CALL();
     auto frameTimelineVsyncId = static_cast<int64_t>(va_arg(args, int64_t));
@@ -1822,7 +1858,7 @@
     return getExtraBufferCount(extraBuffers);
 }
 
-bool Surface::transformToDisplayInverse() {
+bool Surface::transformToDisplayInverse() const {
     return (mTransform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) ==
             NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
 }
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 139dbb7..c4ca399 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -103,6 +103,8 @@
 
     void setSidebandStream(const sp<NativeHandle>& stream);
 
+    uint32_t getLastTransformHint() const;
+
     virtual ~BLASTBufferQueue();
 
 private:
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index a7f7d1d..0ad3075 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -186,6 +186,10 @@
     virtual status_t getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer,
             sp<Fence>* outFence, float outTransformMatrix[16]) override;
 
+    // See IGraphicBufferProducer::getLastQueuedBuffer
+    virtual status_t getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence,
+                                         Rect* outRect, uint32_t* outTransform) override;
+
     // See IGraphicBufferProducer::getFrameTimestamps
     virtual void getFrameTimestamps(FrameEventHistoryDelta* outDelta) override;
 
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index c3b9262..98df834 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -640,6 +640,22 @@
     virtual status_t getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer,
             sp<Fence>* outFence, float outTransformMatrix[16]) = 0;
 
+    // Returns the last queued buffer along with a fence which must signal
+    // before the contents of the buffer are read. If there are no buffers in
+    // the queue, outBuffer will be populated with nullptr and outFence will be
+    // populated with Fence::NO_FENCE
+    //
+    // outRect & outTransform are not modified if outBuffer is null.
+    //
+    // Returns NO_ERROR or the status of the Binder transaction
+    virtual status_t getLastQueuedBuffer([[maybe_unused]] sp<GraphicBuffer>* outBuffer,
+                                         [[maybe_unused]] sp<Fence>* outFence,
+                                         [[maybe_unused]] Rect* outRect,
+                                         [[maybe_unused]] uint32_t* outTransform) {
+        // Too many things implement IGraphicBufferProducer...
+        return UNKNOWN_TRANSACTION;
+    }
+
     // Gets the frame events that haven't already been retrieved.
     virtual void getFrameTimestamps(FrameEventHistoryDelta* /*outDelta*/) {}
 
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index d22bdaa..48885eb 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -276,9 +276,9 @@
     int dispatchAddQueueInterceptor(va_list args);
     int dispatchAddQueryInterceptor(va_list args);
     int dispatchGetLastQueuedBuffer(va_list args);
+    int dispatchGetLastQueuedBuffer2(va_list args);
     int dispatchSetFrameTimelineInfo(va_list args);
     int dispatchGetExtraBufferCount(va_list args);
-    bool transformToDisplayInverse();
 
 protected:
     virtual int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd);
@@ -490,6 +490,8 @@
     // mTransformHint is the transform probably applied to buffers of this
     // window. this is only a hint, actual transform may differ.
     uint32_t mTransformHint;
+    virtual uint32_t getTransformHint() const { return mTransformHint; }
+    bool transformToDisplayInverse() const;
 
     // mProducerControlledByApp whether this buffer producer is controlled
     // by the application
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/InputWindow.cpp b/libs/input/InputWindow.cpp
index 8546bbb..9947720 100644
--- a/libs/input/InputWindow.cpp
+++ b/libs/input/InputWindow.cpp
@@ -57,11 +57,12 @@
             info.frameLeft == frameLeft && info.frameTop == frameTop &&
             info.frameRight == frameRight && info.frameBottom == frameBottom &&
             info.surfaceInset == surfaceInset && info.globalScaleFactor == globalScaleFactor &&
-            info.transform == transform && info.touchableRegion.hasSameRects(touchableRegion) &&
-            info.visible == visible && info.trustedOverlay == trustedOverlay &&
-            info.focusable == focusable && info.touchOcclusionMode == touchOcclusionMode &&
-            info.hasWallpaper == hasWallpaper && info.paused == paused &&
-            info.ownerPid == ownerPid && info.ownerUid == ownerUid &&
+            info.transform == transform && info.displayWidth == displayWidth &&
+            info.displayHeight == displayHeight &&
+            info.touchableRegion.hasSameRects(touchableRegion) && info.visible == visible &&
+            info.trustedOverlay == trustedOverlay && info.focusable == focusable &&
+            info.touchOcclusionMode == touchOcclusionMode && info.hasWallpaper == hasWallpaper &&
+            info.paused == paused && info.ownerPid == ownerPid && info.ownerUid == ownerUid &&
             info.packageName == packageName && info.inputFeatures == inputFeatures &&
             info.displayId == displayId && info.portalToDisplayId == portalToDisplayId &&
             info.replaceTouchableRegionWithCrop == replaceTouchableRegionWithCrop &&
@@ -99,6 +100,8 @@
         parcel->writeFloat(transform.dtdy()) ?:
         parcel->writeFloat(transform.dsdy()) ?:
         parcel->writeFloat(transform.ty()) ?:
+        parcel->writeInt32(displayWidth) ?:
+        parcel->writeInt32(displayHeight) ?:
         parcel->writeBool(visible) ?:
         parcel->writeBool(focusable) ?:
         parcel->writeBool(hasWallpaper) ?:
@@ -153,6 +156,8 @@
         parcel->readFloat(&dtdy) ?:
         parcel->readFloat(&dsdy) ?:
         parcel->readFloat(&ty) ?:
+        parcel->readInt32(&displayWidth) ?:
+        parcel->readInt32(&displayHeight) ?:
         parcel->readBool(&visible) ?:
         parcel->readBool(&focusable) ?:
         parcel->readBool(&hasWallpaper) ?:
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/InputWindow_test.cpp b/libs/input/tests/InputWindow_test.cpp
index c18a17f..493f2f4 100644
--- a/libs/input/tests/InputWindow_test.cpp
+++ b/libs/input/tests/InputWindow_test.cpp
@@ -55,6 +55,8 @@
     i.globalScaleFactor = 0.3;
     i.alpha = 0.7;
     i.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1});
+    i.displayWidth = 1000;
+    i.displayHeight = 2000;
     i.visible = false;
     i.focusable = false;
     i.hasWallpaper = false;
@@ -91,6 +93,8 @@
     ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
     ASSERT_EQ(i.alpha, i2.alpha);
     ASSERT_EQ(i.transform, i2.transform);
+    ASSERT_EQ(i.displayWidth, i2.displayWidth);
+    ASSERT_EQ(i.displayHeight, i2.displayHeight);
     ASSERT_EQ(i.visible, i2.visible);
     ASSERT_EQ(i.focusable, i2.focusable);
     ASSERT_EQ(i.hasWallpaper, i2.hasWallpaper);
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/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index cc82bb4..935eded 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -257,6 +257,7 @@
     NATIVE_WINDOW_SET_QUERY_INTERCEPTOR           = 47,    /* private */
     NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO         = 48,    /* private */
     NATIVE_WINDOW_GET_EXTRA_BUFFER_COUNT          = 49,    /* private */
+    NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2         = 50,    /* private */
     // clang-format on
 };
 
@@ -1067,6 +1068,31 @@
 }
 
 /**
+ * Retrieves the last queued buffer for this window, along with the fence that
+ * fires when the buffer is ready to be read. The cropRect & transform should be applied to the
+ * buffer's content.
+ *
+ * If there was no buffer previously queued, then outBuffer will be NULL and
+ * the value of outFence will be -1.
+ *
+ * Note that if outBuffer is not NULL, then the caller will hold a reference
+ * onto the buffer. Accordingly, the caller must call AHardwareBuffer_release
+ * when the buffer is no longer needed so that the system may reclaim the
+ * buffer.
+ *
+ * \return NO_ERROR on success.
+ * \return NO_MEMORY if there was insufficient memory.
+ * \return STATUS_UNKNOWN_TRANSACTION if this ANativeWindow doesn't support this method, callers
+ *         should fall back to ANativeWindow_getLastQueuedBuffer instead.
+ */
+static inline int ANativeWindow_getLastQueuedBuffer2(ANativeWindow* window,
+                                                     AHardwareBuffer** outBuffer, int* outFence,
+                                                     ARect* outCropRect, uint32_t* outTransform) {
+    return window->perform(window, NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2, outBuffer, outFence,
+                           outCropRect, outTransform);
+}
+
+/**
  * Retrieves an identifier for the next frame to be queued by this window.
  *
  * \return the next frame id.
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index ff4d112..63ed1ba 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -276,7 +276,7 @@
 }
 
 static inline void PrintTo(const LayerSettings& settings, ::std::ostream* os) {
-    *os << "LayerSettings {";
+    *os << "LayerSettings for '" << settings.name.c_str() << "' {";
     *os << "\n    .geometry = ";
     PrintTo(settings.geometry, os);
     *os << "\n    .source = ";
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 0eee564..77e01f4 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -37,10 +37,6 @@
                                      0.f,  0.7f, 0.f, 0.f,
                                      0.f,   0.f, 1.f, 0.f,
                                    67.3f, 52.2f, 0.f, 1.f);
-const auto kScaleYOnly = mat4(1.f,   0.f, 0.f, 0.f,
-                              0.f,  0.7f, 0.f, 0.f,
-                              0.f,   0.f, 1.f, 0.f,
-                              0.f,   0.f, 0.f, 1.f);
 // clang-format on
 // When setting layer.sourceDataspace, whether it matches the destination or not determines whether
 // a color correction effect is added to the shader.
@@ -188,33 +184,51 @@
     }
 }
 
-static void drawTextureScaleLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
-                                   const std::shared_ptr<ExternalTexture>& dstTexture,
-                                   const std::shared_ptr<ExternalTexture>& srcTexture) {
+// The unique feature of these layers is that the boundary is slightly smaller than the rounded
+// rect crop, so the rounded edges intersect that boundary and require a different clipping method.
+static void drawClippedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                              const std::shared_ptr<ExternalTexture>& dstTexture,
+                              const std::shared_ptr<ExternalTexture>& srcTexture) {
     const Rect& displayRect = display.physicalDisplay;
-    FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+    FloatRect rect(0, 0, displayRect.width(), displayRect.height() - 20); // boundary is smaller
+
+    // clang-format off
+    const auto symmetric = mat4(0.9f, 0.f,  0.f, 0.f,
+                                0.f,  0.9f, 0.f, 0.f,
+                                0.f,  0.f,  1.f, 0.f,
+                                8.8f, 8.1f, 0.f, 1.f);
+    const auto asymmetric = mat4(0.9f, 0.f,  0.f, 0.f,
+                                 0.f,  0.7f, 0.f, 0.f,
+                                 0.f,  0.f,  1.f, 0.f,
+                                 8.8f, 8.1f, 0.f, 1.f);
+
+    // clang-format on
     LayerSettings layer{
             .geometry =
                     Geometry{
                             .boundaries = rect,
-                            .roundedCornersCrop = rect,
-                            .positionTransform = kScaleAndTranslate,
-                            .roundedCornersRadius = 300,
+                            .roundedCornersRadius = 27, // larger than the 20 above.
+                            .roundedCornersCrop =
+                                    FloatRect(0, 0, displayRect.width(), displayRect.height()),
                     },
             .source = PixelSource{.buffer =
                                           Buffer{
                                                   .buffer = srcTexture,
+                                                  .isOpaque = 0,
                                                   .maxLuminanceNits = 1000.f,
-                                                  .textureTransform = kScaleYOnly,
                                           }},
             .sourceDataspace = kOtherDataSpace,
     };
 
     auto layers = std::vector<const LayerSettings*>{&layer};
-    for (float alpha : {0.5f, 1.f}) {
-        layer.alpha = alpha,
-        renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                 base::unique_fd(), nullptr);
+    for (auto transform : {symmetric, asymmetric}) {
+        layer.geometry.positionTransform = transform;
+        // In real use, I saw alpha of 1.0 and 0.999, probably a mistake, but cache both shaders.
+        for (float alpha : {0.5f, 1.f}) {
+            layer.alpha = alpha,
+            renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
+                                     base::unique_fd(), nullptr);
+        }
     }
 }
 
@@ -293,7 +307,7 @@
         drawImageLayers(renderengine, display, dstTexture, externalTexture);
 
         // Draw layers for b/185569240.
-        drawTextureScaleLayers(renderengine, display, dstTexture, externalTexture);
+        drawClippedLayers(renderengine, display, dstTexture, externalTexture);
 
         const nsecs_t timeAfter = systemTime();
         const float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 8059bc1..6cc3d37 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -1101,8 +1101,8 @@
 
     SkRRect clip;
     if (cornerRadius > 0) {
-        // it the crop and the bounds are equivalent then we don't need a clip
-        if (bounds == crop) {
+        // it the crop and the bounds are equivalent or there is no crop then we don't need a clip
+        if (bounds == crop || crop.isEmpty()) {
             return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip};
         }
 
@@ -1116,34 +1116,47 @@
 
             const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius);
 
+            const bool leftEqual = bounds.fLeft == crop.fLeft;
+            const bool topEqual = bounds.fTop == crop.fTop;
+            const bool rightEqual = bounds.fRight == crop.fRight;
+            const bool bottomEqual = bounds.fBottom == crop.fBottom;
+
             // compute the UpperLeft corner radius
-            if (bounds.fLeft == crop.fLeft && bounds.fTop == crop.fTop) {
+            if (leftEqual && topEqual) {
                 radii[0].set(cornerRadius, cornerRadius);
-            } else if (bounds.fLeft > insetCrop.fLeft && bounds.fTop > insetCrop.fTop) {
+            } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) ||
+                       (topEqual && bounds.fLeft >= insetCrop.fLeft) ||
+                       insetCrop.contains(bounds.fLeft, bounds.fTop)) {
                 radii[0].set(0, 0);
             } else {
                 intersectionIsRoundRect = false;
             }
             // compute the UpperRight corner radius
-            if (bounds.fRight == crop.fRight && bounds.fTop == crop.fTop) {
+            if (rightEqual && topEqual) {
                 radii[1].set(cornerRadius, cornerRadius);
-            } else if (bounds.fRight < insetCrop.fRight && bounds.fTop > insetCrop.fTop) {
+            } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) ||
+                       (topEqual && bounds.fRight <= insetCrop.fRight) ||
+                       insetCrop.contains(bounds.fRight, bounds.fTop)) {
                 radii[1].set(0, 0);
             } else {
                 intersectionIsRoundRect = false;
             }
             // compute the BottomRight corner radius
-            if (bounds.fRight == crop.fRight && bounds.fBottom == crop.fBottom) {
+            if (rightEqual && bottomEqual) {
                 radii[2].set(cornerRadius, cornerRadius);
-            } else if (bounds.fRight < insetCrop.fRight && bounds.fBottom < insetCrop.fBottom) {
+            } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) ||
+                       (bottomEqual && bounds.fRight <= insetCrop.fRight) ||
+                       insetCrop.contains(bounds.fRight, bounds.fBottom)) {
                 radii[2].set(0, 0);
             } else {
                 intersectionIsRoundRect = false;
             }
             // compute the BottomLeft corner radius
-            if (bounds.fLeft == crop.fLeft && bounds.fBottom == crop.fBottom) {
+            if (leftEqual && bottomEqual) {
                 radii[3].set(cornerRadius, cornerRadius);
-            } else if (bounds.fLeft > insetCrop.fLeft && bounds.fBottom < insetCrop.fBottom) {
+            } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) ||
+                       (bottomEqual && bounds.fLeft >= insetCrop.fLeft) ||
+                       insetCrop.contains(bounds.fLeft, bounds.fBottom)) {
                 radii[3].set(0, 0);
             } else {
                 intersectionIsRoundRect = false;
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 636fbde..9dc9beb 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -36,6 +36,7 @@
 using android::hardware::graphics::mapper::V4_0::BufferDescriptor;
 using android::hardware::graphics::mapper::V4_0::Error;
 using android::hardware::graphics::mapper::V4_0::IMapper;
+using AidlDataspace = ::aidl::android::hardware::graphics::common::Dataspace;
 using BufferDump = android::hardware::graphics::mapper::V4_0::IMapper::BufferDump;
 using MetadataDump = android::hardware::graphics::mapper::V4_0::IMapper::MetadataDump;
 using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
@@ -597,7 +598,7 @@
     if (!outDataspace) {
         return BAD_VALUE;
     }
-    aidl::android::hardware::graphics::common::Dataspace dataspace;
+    AidlDataspace dataspace;
     status_t error = get(bufferHandle, gralloc4::MetadataType_Dataspace, gralloc4::decodeDataspace,
                          &dataspace);
     if (error) {
@@ -841,6 +842,7 @@
     uint32_t pixelFormatFourCC;
     uint64_t pixelFormatModifier;
     uint64_t usage;
+    AidlDataspace dataspace;
     uint64_t allocationSize;
     uint64_t protectedContent;
     ExtendableType compression;
@@ -892,6 +894,11 @@
     if (error != NO_ERROR) {
         return error;
     }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::DATASPACE,
+                               gralloc4::decodeDataspace, &dataspace);
+    if (error != NO_ERROR) {
+        return error;
+    }
     error = metadataDumpHelper(bufferDump, StandardMetadataType::ALLOCATION_SIZE,
                                gralloc4::decodeAllocationSize, &allocationSize);
     if (error != NO_ERROR) {
@@ -932,6 +939,7 @@
              << "KiB, w/h:" << width << "x" << height << ", usage: 0x" << std::hex << usage
              << std::dec << ", req fmt:" << static_cast<int32_t>(pixelFormatRequested)
              << ", fourcc/mod:" << pixelFormatFourCC << "/" << pixelFormatModifier
+             << ", dataspace: 0x" << std::hex << static_cast<uint32_t>(dataspace)
              << ", compressed: ";
 
     if (less) {
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.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 6ed9593..881024f 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -296,7 +296,7 @@
 volatile int32_t DispatchEntry::sNextSeqAtomic;
 
 DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                             ui::Transform transform, float globalScaleFactor, vec2 displaySize)
+                             ui::Transform transform, float globalScaleFactor, int2 displaySize)
       : seq(nextSeq()),
         eventEntry(std::move(eventEntry)),
         targetFlags(targetFlags),
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 45c5e24..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.
@@ -215,7 +215,7 @@
     int32_t targetFlags;
     ui::Transform transform;
     float globalScaleFactor;
-    vec2 displaySize;
+    int2 displaySize;
     // Both deliveryTime and timeoutTime are only populated when the entry is sent to the app,
     // and will be undefined before that.
     nsecs_t deliveryTime; // time when the event was actually delivered
@@ -228,7 +228,7 @@
     int32_t resolvedFlags;
 
     DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                  ui::Transform transform, float globalScaleFactor, vec2 displaySize);
+                  ui::Transform transform, float globalScaleFactor, int2 displaySize);
 
     inline bool hasForegroundTarget() const { return targetFlags & InputTarget::FLAG_FOREGROUND; }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 8bc877f..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();
@@ -2385,7 +2436,7 @@
         inputTarget.flags = targetFlags;
         inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
         inputTarget.displaySize =
-                vec2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
+                int2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
         inputTargets.push_back(inputTarget);
         it = inputTargets.end() - 1;
     }
@@ -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/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 2543852..1c4980b 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -101,7 +101,7 @@
     float globalScaleFactor = 1.0f;
 
     // Display-size in its natural rotation. Used for compatibility transform of raw coordinates.
-    vec2 displaySize = {AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE};
+    int2 displaySize = {AMOTION_EVENT_INVALID_DISPLAY_SIZE, AMOTION_EVENT_INVALID_DISPLAY_SIZE};
 
     // The subset of pointer ids to include in motion events dispatched to this input target
     // if FLAG_SPLIT is set.
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/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index fcf8299..54daa10 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -682,7 +682,10 @@
 }
 
 bool BufferStateLayer::latchSidebandStream(bool& recomputeVisibleRegions) {
-    if (mSidebandStreamChanged.exchange(false)) {
+    // We need to update the sideband stream if the layer has both a buffer and a sideband stream.
+    const bool updateSidebandStream = hasFrameUpdate() && mSidebandStream.get();
+
+    if (mSidebandStreamChanged.exchange(false) || updateSidebandStream) {
         const State& s(getDrawingState());
         // mSidebandStreamChanged was true
         mSidebandStream = s.sidebandStream;
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/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 8c8331c..953eb76 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -192,16 +192,7 @@
     if (const auto halDisplayId = HalDisplayId::tryCast(mId);
         outputLayer && !mIsDisconnected && halDisplayId) {
         auto& hwc = getCompositionEngine().getHwComposer();
-        // Note: For the moment we ensure it is safe to take a reference to the
-        // HWComposer implementation by destroying all the OutputLayers (and
-        // hence the HWC2::Layers they own) before setting a new HWComposer. See
-        // for example SurfaceFlinger::updateVrFlinger().
-        // TODO(b/121291683): Make this safer.
-        auto hwcLayer =
-                std::shared_ptr<HWC2::Layer>(hwc.createLayer(*halDisplayId),
-                                             [&hwc, id = *halDisplayId](HWC2::Layer* layer) {
-                                                 hwc.destroyLayer(id, layer);
-                                             });
+        auto hwcLayer = hwc.createLayer(*halDisplayId);
         ALOGE_IF(!hwcLayer, "Failed to create a HWC layer for a HWC supported display %s",
                  getName().c_str());
         outputLayer->setHwcLayer(std::move(hwcLayer));
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/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index e63d09a..e12cb57 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -533,16 +533,15 @@
 
 TEST_F(DisplayCreateOutputLayerTest, setsHwcLayer) {
     sp<mock::LayerFE> layerFE = new StrictMock<mock::LayerFE>();
-    StrictMock<HWC2::mock::Layer> hwcLayer;
+    auto hwcLayer = std::make_shared<StrictMock<HWC2::mock::Layer>>();
 
     EXPECT_CALL(mHwComposer, createLayer(HalDisplayId(DEFAULT_DISPLAY_ID)))
-            .WillOnce(Return(&hwcLayer));
+            .WillOnce(Return(hwcLayer));
 
     auto outputLayer = mDisplay->createOutputLayer(layerFE);
 
-    EXPECT_EQ(&hwcLayer, outputLayer->getHwcLayer());
+    EXPECT_EQ(hwcLayer.get(), outputLayer->getHwcLayer());
 
-    EXPECT_CALL(mHwComposer, destroyLayer(HalDisplayId(DEFAULT_DISPLAY_ID), &hwcLayer));
     outputLayer.reset();
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 74d4b92..cd2d09e 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -48,8 +48,7 @@
     MOCK_METHOD3(allocateVirtualDisplay,
                  std::optional<DisplayId>(uint32_t, uint32_t, ui::PixelFormat*));
     MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId));
-    MOCK_METHOD1(createLayer, HWC2::Layer*(HalDisplayId));
-    MOCK_METHOD2(destroyLayer, void(HalDisplayId, HWC2::Layer*));
+    MOCK_METHOD1(createLayer, std::shared_ptr<HWC2::Layer>(HalDisplayId));
     MOCK_METHOD4(getDeviceCompositionChanges,
                  status_t(HalDisplayId, bool, std::chrono::steady_clock::time_point,
                           std::optional<android::HWComposer::DeviceRequestedChanges>*));
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
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index d04b5f7..27146ab 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -78,7 +78,19 @@
 }
 
 Display::~Display() {
-    mLayers.clear();
+    // Note: The calls to onOwningDisplayDestroyed() are allowed (and expected)
+    // to call Display::onLayerDestroyed(). As that call removes entries from
+    // mLayers, we do not want to have a for loop directly over it here. Since
+    // the end goal is an empty mLayers anyway, we just go ahead and swap an
+    // initially empty local container with mLayers, and then enumerate
+    // the contents of the local container.
+    Layers destroyingLayers;
+    std::swap(mLayers, destroyingLayers);
+    for (const auto& [_, weakLayer] : destroyingLayers) {
+        if (std::shared_ptr layer = weakLayer.lock()) {
+            layer->onOwningDisplayDestroyed();
+        }
+    }
 
     Error error = Error::NONE;
     const char* msg;
@@ -110,29 +122,21 @@
     return static_cast<Error>(intError);
 }
 
-Error Display::createLayer(HWC2::Layer** outLayer) {
-    if (!outLayer) {
-        return Error::BAD_PARAMETER;
-    }
+base::expected<std::shared_ptr<HWC2::Layer>, hal::Error> Display::createLayer() {
     HWLayerId layerId = 0;
     auto intError = mComposer.createLayer(mId, &layerId);
     auto error = static_cast<Error>(intError);
     if (error != Error::NONE) {
-        return error;
+        return base::unexpected(error);
     }
 
-    auto layer = std::make_unique<impl::Layer>(mComposer, mCapabilities, mId, layerId);
-    *outLayer = layer.get();
-    mLayers.emplace(layerId, std::move(layer));
-    return Error::NONE;
+    auto layer = std::make_shared<impl::Layer>(mComposer, mCapabilities, *this, layerId);
+    mLayers.emplace(layerId, layer);
+    return layer;
 }
 
-Error Display::destroyLayer(HWC2::Layer* layer) {
-    if (!layer) {
-        return Error::BAD_PARAMETER;
-    }
-    mLayers.erase(layer->getId());
-    return Error::NONE;
+void Display::onLayerDestroyed(hal::HWLayerId layerId) {
+    mLayers.erase(layerId);
 }
 
 bool Display::isVsyncPeriodSwitchSupported() const {
@@ -161,7 +165,7 @@
             auto type = types[element];
             ALOGV("getChangedCompositionTypes: adding %" PRIu64 " %s",
                     layer->getId(), to_string(type).c_str());
-            outTypes->emplace(layer, type);
+            outTypes->emplace(layer.get(), type);
         } else {
             ALOGE("getChangedCompositionTypes: invalid layer %" PRIu64 " found"
                     " on display %" PRIu64, layerIds[element], mId);
@@ -254,7 +258,7 @@
         if (layer) {
             auto layerRequest =
                     static_cast<LayerRequest>(layerRequests[element]);
-            outLayerRequests->emplace(layer, layerRequest);
+            outLayerRequests->emplace(layer.get(), layerRequest);
         } else {
             ALOGE("getRequests: invalid layer %" PRIu64 " found on display %"
                     PRIu64, layerIds[element], mId);
@@ -340,7 +344,7 @@
         auto layer = getLayerById(layerIds[element]);
         if (layer) {
             sp<Fence> fence(new Fence(fenceFds[element]));
-            releaseFences.emplace(layer, fence);
+            releaseFences.emplace(layer.get(), fence);
         } else {
             ALOGE("getReleaseFences: invalid layer %" PRIu64
                     " found on display %" PRIu64, layerIds[element], mId);
@@ -550,12 +554,9 @@
 
 // Other Display methods
 
-HWC2::Layer* Display::getLayerById(HWLayerId id) const {
-    if (mLayers.count(id) == 0) {
-        return nullptr;
-    }
-
-    return mLayers.at(id).get();
+std::shared_ptr<HWC2::Layer> Display::getLayerById(HWLayerId id) const {
+    auto it = mLayers.find(id);
+    return it != mLayers.end() ? it->second.lock() : nullptr;
 }
 } // namespace impl
 
@@ -566,47 +567,78 @@
 namespace impl {
 
 Layer::Layer(android::Hwc2::Composer& composer, const std::unordered_set<Capability>& capabilities,
-             HWDisplayId displayId, HWLayerId layerId)
+             HWC2::Display& display, HWLayerId layerId)
       : mComposer(composer),
         mCapabilities(capabilities),
-        mDisplayId(displayId),
+        mDisplay(&display),
         mId(layerId),
         mColorMatrix(android::mat4()) {
-    ALOGV("Created layer %" PRIu64 " on display %" PRIu64, layerId, displayId);
+    ALOGV("Created layer %" PRIu64 " on display %" PRIu64, layerId, display.getId());
 }
 
 Layer::~Layer()
 {
-    auto intError = mComposer.destroyLayer(mDisplayId, mId);
+    onOwningDisplayDestroyed();
+}
+
+void Layer::onOwningDisplayDestroyed() {
+    // Note: onOwningDisplayDestroyed() may be called to perform cleanup by
+    // either the Layer dtor or by the Display dtor and must be safe to call
+    // from either path. In particular, the call to Display::onLayerDestroyed()
+    // is expected to be safe to do,
+
+    if (CC_UNLIKELY(!mDisplay)) {
+        return;
+    }
+
+    mDisplay->onLayerDestroyed(mId);
+
+    // Note: If the HWC display was actually disconnected, these calls are will
+    // return an error. We always make them as there may be other reasons for
+    // the HWC2::Display to be destroyed.
+    auto intError = mComposer.destroyLayer(mDisplay->getId(), mId);
     auto error = static_cast<Error>(intError);
     ALOGE_IF(error != Error::NONE,
              "destroyLayer(%" PRIu64 ", %" PRIu64 ")"
              " failed: %s (%d)",
-             mDisplayId, mId, to_string(error).c_str(), intError);
+             mDisplay->getId(), mId, to_string(error).c_str(), intError);
+
+    mDisplay = nullptr;
 }
 
 Error Layer::setCursorPosition(int32_t x, int32_t y)
 {
-    auto intError = mComposer.setCursorPosition(mDisplayId, mId, x, y);
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
+    auto intError = mComposer.setCursorPosition(mDisplay->getId(), mId, x, y);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setBuffer(uint32_t slot, const sp<GraphicBuffer>& buffer,
         const sp<Fence>& acquireFence)
 {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     if (buffer == nullptr && mBufferSlot == slot) {
         return Error::NONE;
     }
     mBufferSlot = slot;
 
     int32_t fenceFd = acquireFence->dup();
-    auto intError = mComposer.setLayerBuffer(mDisplayId, mId, slot, buffer,
-                                             fenceFd);
+    auto intError = mComposer.setLayerBuffer(mDisplay->getId(), mId, slot, buffer, fenceFd);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setSurfaceDamage(const Region& damage)
 {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     if (damage.isRect() && mDamageRegion.isRect() &&
         (damage.getBounds() == mDamageRegion.getBounds())) {
         return Error::NONE;
@@ -617,8 +649,8 @@
     // rects for HWC
     Hwc2::Error intError = Hwc2::Error::NONE;
     if (damage.isRect() && damage.getBounds() == Rect::INVALID_RECT) {
-        intError = mComposer.setLayerSurfaceDamage(mDisplayId,
-                mId, std::vector<Hwc2::IComposerClient::Rect>());
+        intError = mComposer.setLayerSurfaceDamage(mDisplay->getId(), mId,
+                                                   std::vector<Hwc2::IComposerClient::Rect>());
     } else {
         size_t rectCount = 0;
         auto rectArray = damage.getArray(&rectCount);
@@ -629,7 +661,7 @@
                     rectArray[rect].right, rectArray[rect].bottom});
         }
 
-        intError = mComposer.setLayerSurfaceDamage(mDisplayId, mId, hwcRects);
+        intError = mComposer.setLayerSurfaceDamage(mDisplay->getId(), mId, hwcRects);
     }
 
     return static_cast<Error>(intError);
@@ -637,34 +669,54 @@
 
 Error Layer::setBlendMode(BlendMode mode)
 {
-    auto intError = mComposer.setLayerBlendMode(mDisplayId, mId, mode);
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
+    auto intError = mComposer.setLayerBlendMode(mDisplay->getId(), mId, mode);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setColor(Color color) {
-    auto intError = mComposer.setLayerColor(mDisplayId, mId, color);
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
+    auto intError = mComposer.setLayerColor(mDisplay->getId(), mId, color);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setCompositionType(Composition type)
 {
-    auto intError = mComposer.setLayerCompositionType(mDisplayId, mId, type);
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
+    auto intError = mComposer.setLayerCompositionType(mDisplay->getId(), mId, type);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setDataspace(Dataspace dataspace)
 {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     if (dataspace == mDataSpace) {
         return Error::NONE;
     }
     mDataSpace = dataspace;
-    auto intError = mComposer.setLayerDataspace(mDisplayId, mId, mDataSpace);
+    auto intError = mComposer.setLayerDataspace(mDisplay->getId(), mId, mDataSpace);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setPerFrameMetadata(const int32_t supportedPerFrameMetadata,
         const android::HdrMetadata& metadata)
 {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     if (metadata == mHdrMetadata) {
         return Error::NONE;
     }
@@ -705,7 +757,7 @@
     }
 
     Error error = static_cast<Error>(
-            mComposer.setLayerPerFrameMetadata(mDisplayId, mId, perFrameMetadatas));
+            mComposer.setLayerPerFrameMetadata(mDisplay->getId(), mId, perFrameMetadatas));
 
     if (validTypes & HdrMetadata::HDR10PLUS) {
         if (CC_UNLIKELY(mHdrMetadata.hdr10plus.size() == 0)) {
@@ -715,8 +767,9 @@
         std::vector<Hwc2::PerFrameMetadataBlob> perFrameMetadataBlobs;
         perFrameMetadataBlobs.push_back(
                 {Hwc2::PerFrameMetadataKey::HDR10_PLUS_SEI, mHdrMetadata.hdr10plus});
-        Error setMetadataBlobsError = static_cast<Error>(
-                mComposer.setLayerPerFrameMetadataBlobs(mDisplayId, mId, perFrameMetadataBlobs));
+        Error setMetadataBlobsError =
+                static_cast<Error>(mComposer.setLayerPerFrameMetadataBlobs(mDisplay->getId(), mId,
+                                                                           perFrameMetadataBlobs));
         if (error == Error::NONE) {
             return setMetadataBlobsError;
         }
@@ -726,46 +779,70 @@
 
 Error Layer::setDisplayFrame(const Rect& frame)
 {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     Hwc2::IComposerClient::Rect hwcRect{frame.left, frame.top,
         frame.right, frame.bottom};
-    auto intError = mComposer.setLayerDisplayFrame(mDisplayId, mId, hwcRect);
+    auto intError = mComposer.setLayerDisplayFrame(mDisplay->getId(), mId, hwcRect);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setPlaneAlpha(float alpha)
 {
-    auto intError = mComposer.setLayerPlaneAlpha(mDisplayId, mId, alpha);
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
+    auto intError = mComposer.setLayerPlaneAlpha(mDisplay->getId(), mId, alpha);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setSidebandStream(const native_handle_t* stream)
 {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     if (mCapabilities.count(Capability::SIDEBAND_STREAM) == 0) {
         ALOGE("Attempted to call setSidebandStream without checking that the "
                 "device supports sideband streams");
         return Error::UNSUPPORTED;
     }
-    auto intError = mComposer.setLayerSidebandStream(mDisplayId, mId, stream);
+    auto intError = mComposer.setLayerSidebandStream(mDisplay->getId(), mId, stream);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setSourceCrop(const FloatRect& crop)
 {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     Hwc2::IComposerClient::FRect hwcRect{
         crop.left, crop.top, crop.right, crop.bottom};
-    auto intError = mComposer.setLayerSourceCrop(mDisplayId, mId, hwcRect);
+    auto intError = mComposer.setLayerSourceCrop(mDisplay->getId(), mId, hwcRect);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setTransform(Transform transform)
 {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     auto intTransform = static_cast<Hwc2::Transform>(transform);
-    auto intError = mComposer.setLayerTransform(mDisplayId, mId, intTransform);
+    auto intError = mComposer.setLayerTransform(mDisplay->getId(), mId, intTransform);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setVisibleRegion(const Region& region)
 {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     if (region.isRect() && mVisibleRegion.isRect() &&
         (region.getBounds() == mVisibleRegion.getBounds())) {
         return Error::NONE;
@@ -781,22 +858,30 @@
                 rectArray[rect].right, rectArray[rect].bottom});
     }
 
-    auto intError = mComposer.setLayerVisibleRegion(mDisplayId, mId, hwcRects);
+    auto intError = mComposer.setLayerVisibleRegion(mDisplay->getId(), mId, hwcRects);
     return static_cast<Error>(intError);
 }
 
 Error Layer::setZOrder(uint32_t z)
 {
-    auto intError = mComposer.setLayerZOrder(mDisplayId, mId, z);
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
+    auto intError = mComposer.setLayerZOrder(mDisplay->getId(), mId, z);
     return static_cast<Error>(intError);
 }
 
 // Composer HAL 2.3
 Error Layer::setColorTransform(const android::mat4& matrix) {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
     if (matrix == mColorMatrix) {
         return Error::NONE;
     }
-    auto intError = mComposer.setLayerColorTransform(mDisplayId, mId, matrix.asArray());
+    auto intError = mComposer.setLayerColorTransform(mDisplay->getId(), mId, matrix.asArray());
     Error error = static_cast<Error>(intError);
     if (error != Error::NONE) {
         return error;
@@ -808,7 +893,12 @@
 // Composer HAL 2.4
 Error Layer::setLayerGenericMetadata(const std::string& name, bool mandatory,
                                      const std::vector<uint8_t>& value) {
-    auto intError = mComposer.setLayerGenericMetadata(mDisplayId, mId, name, mandatory, value);
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
+    auto intError =
+            mComposer.setLayerGenericMetadata(mDisplay->getId(), mId, name, mandatory, value);
     return static_cast<Error>(intError);
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index e7bf286..fae95e7 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <android-base/expected.h>
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
 #include <ui/HdrCapabilities.h>
@@ -84,10 +85,11 @@
     virtual void setConnected(bool connected) = 0; // For use by Device only
     virtual const std::unordered_set<hal::DisplayCapability>& getCapabilities() const = 0;
     virtual bool isVsyncPeriodSwitchSupported() const = 0;
+    virtual void onLayerDestroyed(hal::HWLayerId layerId) = 0;
 
     [[clang::warn_unused_result]] virtual hal::Error acceptChanges() = 0;
-    [[clang::warn_unused_result]] virtual hal::Error createLayer(Layer** outLayer) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error destroyLayer(Layer* layer) = 0;
+    [[clang::warn_unused_result]] virtual base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>
+    createLayer() = 0;
     [[clang::warn_unused_result]] virtual hal::Error getChangedCompositionTypes(
             std::unordered_map<Layer*, hal::Composition>* outTypes) = 0;
     [[clang::warn_unused_result]] virtual hal::Error getColorModes(
@@ -152,6 +154,8 @@
 
 namespace impl {
 
+class Layer;
+
 class Display : public HWC2::Display {
 public:
     Display(android::Hwc2::Composer&, const std::unordered_set<hal::Capability>&, hal::HWDisplayId,
@@ -160,10 +164,9 @@
 
     // Required by HWC2
     hal::Error acceptChanges() override;
-    hal::Error createLayer(Layer** outLayer) override;
-    hal::Error destroyLayer(Layer*) override;
+    base::expected<std::shared_ptr<HWC2::Layer>, hal::Error> createLayer() override;
     hal::Error getChangedCompositionTypes(
-            std::unordered_map<Layer*, hal::Composition>* outTypes) override;
+            std::unordered_map<HWC2::Layer*, hal::Composition>* outTypes) override;
     hal::Error getColorModes(std::vector<hal::ColorMode>* outModes) const override;
     // Returns a bitmask which contains HdrMetadata::Type::*.
     int32_t getSupportedPerFrameMetadata() const override;
@@ -174,7 +177,7 @@
     hal::Error getName(std::string* outName) const override;
     hal::Error getRequests(
             hal::DisplayRequest* outDisplayRequests,
-            std::unordered_map<Layer*, hal::LayerRequest>* outLayerRequests) override;
+            std::unordered_map<HWC2::Layer*, hal::LayerRequest>* outLayerRequests) override;
     hal::Error getConnectionType(ui::DisplayConnectionType*) const override;
     hal::Error supportsDoze(bool* outSupport) const override;
     hal::Error getHdrCapabilities(android::HdrCapabilities* outCapabilities) const override;
@@ -185,8 +188,8 @@
                                                 uint64_t maxFrames) const override;
     hal::Error getDisplayedContentSample(uint64_t maxFrames, uint64_t timestamp,
                                          android::DisplayedFrameStats* outStats) const override;
-    hal::Error getReleaseFences(
-            std::unordered_map<Layer*, android::sp<android::Fence>>* outFences) const override;
+    hal::Error getReleaseFences(std::unordered_map<HWC2::Layer*, android::sp<android::Fence>>*
+                                        outFences) const override;
     hal::Error present(android::sp<android::Fence>* outPresentFence) override;
     hal::Error setClientTarget(uint32_t slot, const android::sp<android::GraphicBuffer>& target,
                                const android::sp<android::Fence>& acquireFence,
@@ -218,13 +221,14 @@
     const std::unordered_set<hal::DisplayCapability>& getCapabilities() const override {
         return mDisplayCapabilities;
     };
-    virtual bool isVsyncPeriodSwitchSupported() const override;
+    bool isVsyncPeriodSwitchSupported() const override;
+    void onLayerDestroyed(hal::HWLayerId layerId) override;
 
 private:
 
     // This may fail (and return a null pointer) if no layer with this ID exists
     // on this display
-    Layer* getLayerById(hal::HWLayerId) const;
+    std::shared_ptr<HWC2::Layer> getLayerById(hal::HWLayerId id) const;
 
     friend android::TestableSurfaceFlinger;
 
@@ -240,7 +244,8 @@
     hal::DisplayType mType;
     bool mIsConnected = false;
 
-    std::unordered_map<hal::HWLayerId, std::unique_ptr<Layer>> mLayers;
+    using Layers = std::unordered_map<hal::HWLayerId, std::weak_ptr<HWC2::impl::Layer>>;
+    Layers mLayers;
 
     std::once_flag mDisplayCapabilityQueryFlag;
     std::unordered_set<hal::DisplayCapability> mDisplayCapabilities;
@@ -295,10 +300,12 @@
 class Layer : public HWC2::Layer {
 public:
     Layer(android::Hwc2::Composer& composer,
-          const std::unordered_set<hal::Capability>& capabilities, hal::HWDisplayId displayId,
+          const std::unordered_set<hal::Capability>& capabilities, HWC2::Display& display,
           hal::HWLayerId layerId);
     ~Layer() override;
 
+    void onOwningDisplayDestroyed();
+
     hal::HWLayerId getId() const override { return mId; }
 
     hal::Error setCursorPosition(int32_t x, int32_t y) override;
@@ -334,7 +341,7 @@
     android::Hwc2::Composer& mComposer;
     const std::unordered_set<hal::Capability>& mCapabilities;
 
-    hal::HWDisplayId mDisplayId;
+    HWC2::Display* mDisplay;
     hal::HWLayerId mId;
 
     // Cached HWC2 data, to ensure the same commands aren't sent to the HWC
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index dc4839e..36876dc 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -305,20 +305,15 @@
     return value;
 }
 
-HWC2::Layer* HWComposer::createLayer(HalDisplayId displayId) {
+std::shared_ptr<HWC2::Layer> HWComposer::createLayer(HalDisplayId displayId) {
     RETURN_IF_INVALID_DISPLAY(displayId, nullptr);
 
-    HWC2::Layer* layer;
-    auto error = mDisplayData[displayId].hwcDisplay->createLayer(&layer);
-    RETURN_IF_HWC_ERROR(error, displayId, nullptr);
-    return layer;
-}
-
-void HWComposer::destroyLayer(HalDisplayId displayId, HWC2::Layer* layer) {
-    RETURN_IF_INVALID_DISPLAY(displayId);
-
-    auto error = mDisplayData[displayId].hwcDisplay->destroyLayer(layer);
-    RETURN_IF_HWC_ERROR(error, displayId);
+    auto expected = mDisplayData[displayId].hwcDisplay->createLayer();
+    if (!expected.has_value()) {
+        auto error = std::move(expected).error();
+        RETURN_IF_HWC_ERROR(error, displayId, nullptr);
+    }
+    return std::move(expected).value();
 }
 
 bool HWComposer::isConnected(PhysicalDisplayId displayId) const {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 5bad529..d0c0c11 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -116,9 +116,7 @@
     virtual void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId) = 0;
 
     // Attempts to create a new layer on this display
-    virtual HWC2::Layer* createLayer(HalDisplayId) = 0;
-    // Destroy a previously created layer
-    virtual void destroyLayer(HalDisplayId, HWC2::Layer*) = 0;
+    virtual std::shared_ptr<HWC2::Layer> createLayer(HalDisplayId) = 0;
 
     // Gets any required composition change requests from the HWC device.
     //
@@ -264,9 +262,7 @@
     void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId) override;
 
     // Attempts to create a new layer on this display
-    HWC2::Layer* createLayer(HalDisplayId) override;
-    // Destroy a previously created layer
-    void destroyLayer(HalDisplayId, HWC2::Layer*) override;
+    std::shared_ptr<HWC2::Layer> createLayer(HalDisplayId) override;
 
     status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index ba03c89..34cc389 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -272,7 +272,7 @@
 
     // Used for sanitizing the heuristic data. If two frames are less than
     // this period apart from each other they'll be considered as duplicates.
-    static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(120.f).getPeriodNsecs();
+    static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(240.f).getPeriodNsecs();
     // Used for sanitizing the heuristic data. If two frames are more than
     // this period apart from each other, the interval between them won't be
     // taken into account when calculating average frame rate.
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 88fb811..b33434e 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -94,6 +94,7 @@
         "VSyncReactorTest.cpp",
         "VsyncConfigurationTest.cpp",
         "mock/DisplayHardware/MockComposer.cpp",
+        "mock/DisplayHardware/MockHWC2.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
         "mock/MockEventThread.cpp",
         "mock/MockFrameTimeline.cpp",
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 6b82170..cbf8cc2 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -37,6 +37,7 @@
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
 #include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockHWC2.h"
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
@@ -120,13 +121,19 @@
     static constexpr hal::HWLayerId kLayerId = static_cast<hal::HWLayerId>(1002);
 
     HWComposerLayerTest(const std::unordered_set<hal::Capability>& capabilities)
-          : mCapabilies(capabilities) {}
+          : mCapabilies(capabilities) {
+        EXPECT_CALL(mDisplay, getId()).WillRepeatedly(Return(kDisplayId));
+    }
 
-    ~HWComposerLayerTest() override { EXPECT_CALL(*mHal, destroyLayer(kDisplayId, kLayerId)); }
+    ~HWComposerLayerTest() override {
+        EXPECT_CALL(mDisplay, onLayerDestroyed(kLayerId));
+        EXPECT_CALL(*mHal, destroyLayer(kDisplayId, kLayerId));
+    }
 
     std::unique_ptr<Hwc2::mock::Composer> mHal{new StrictMock<Hwc2::mock::Composer>()};
     const std::unordered_set<hal::Capability> mCapabilies;
-    HWC2::impl::Layer mLayer{*mHal, mCapabilies, kDisplayId, kLayerId};
+    StrictMock<HWC2::mock::Display> mDisplay;
+    HWC2::impl::Layer mLayer{*mHal, mCapabilies, mDisplay, kLayerId};
 };
 
 struct HWComposerLayerGenericMetadataTest : public HWComposerLayerTest {
@@ -176,4 +183,4 @@
 }
 
 } // namespace
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index 325fb8f..d6ce5e2 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -126,7 +126,7 @@
     std::deque<FrameTimeData> frameTimes;
     constexpr auto kExpectedFps = Fps(50.0f);
     constexpr auto kExpectedPeriod = kExpectedFps.getPeriodNsecs();
-    constexpr auto kSmallPeriod = Fps(150.0f).getPeriodNsecs();
+    constexpr auto kSmallPeriod = Fps(250.0f).getPeriodNsecs();
     constexpr int kNumIterations = 10;
     for (int i = 1; i <= kNumIterations; i++) {
         frameTimes.push_back(FrameTimeData{.presentTime = kExpectedPeriod * i,
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 0b70f27..7ace70a 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -1450,12 +1450,12 @@
                                                .seamlessness = Seamlessness::OnlySeamless,
                                                .weight = 1.0f,
                                                .focused = true}};
-    auto& seamedLayer = layers[0];
 
     ASSERT_EQ(HWC_CONFIG_ID_50,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getModeId());
 
+    auto& seamedLayer = layers[0];
     seamedLayer.name = "30Hz ExplicitDefault", seamedLayer.desiredRefreshRate = Fps(30.0f);
     refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_30);
 
@@ -1464,6 +1464,27 @@
                       .getModeId());
 }
 
+TEST_F(RefreshRateConfigsTest, minLayersDontTrigerSeamedSwitch) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_90);
+
+    // Allow group switching.
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.name = "Min",
+                                                                 .vote = LayerVoteType::Min,
+                                                                 .weight = 1.f,
+                                                                 .focused = true}};
+
+    ASSERT_EQ(HWC_CONFIG_ID_90,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+                      .getModeId());
+}
+
 TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) {
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(m30_60_90Device,
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.cpp
new file mode 100644
index 0000000..2647bf4
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "MockHWC2"
+
+#include "mock/DisplayHardware/MockHWC2.h"
+
+namespace android::HWC2::mock {
+
+// Explicit default instantiation is recommended.
+Display::Display() = default;
+Display::~Display() = default;
+
+Layer::Layer() = default;
+Layer::~Layer() = default;
+
+} // namespace android::HWC2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
new file mode 100644
index 0000000..c3919d9
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "DisplayHardware/HWC2.h"
+
+namespace android::HWC2::mock {
+
+class Display : public HWC2::Display {
+public:
+    Display();
+    ~Display() override;
+
+    MOCK_METHOD(hal::HWDisplayId, getId, (), (const, override));
+    MOCK_METHOD(bool, isConnected, (), (const, override));
+    MOCK_METHOD(void, setConnected, (bool), (override));
+    MOCK_METHOD(const std::unordered_set<hal::DisplayCapability> &, getCapabilities, (),
+                (const, override));
+    MOCK_METHOD(bool, isVsyncPeriodSwitchSupported, (), (const, override));
+    MOCK_METHOD(void, onLayerDestroyed, (hal::HWLayerId), (override));
+
+    MOCK_METHOD(hal::Error, acceptChanges, (), (override));
+    MOCK_METHOD((base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>), createLayer, (),
+                (override));
+    MOCK_METHOD(hal::Error, getChangedCompositionTypes,
+                ((std::unordered_map<Layer *, hal::Composition> *)), (override));
+    MOCK_METHOD(hal::Error, getColorModes, (std::vector<hal::ColorMode> *), (const, override));
+    MOCK_METHOD(int32_t, getSupportedPerFrameMetadata, (), (const, override));
+    MOCK_METHOD(hal::Error, getRenderIntents, (hal::ColorMode, std::vector<hal::RenderIntent> *),
+                (const, override));
+    MOCK_METHOD(hal::Error, getDataspaceSaturationMatrix, (hal::Dataspace, android::mat4 *),
+                (override));
+    MOCK_METHOD(hal::Error, getName, (std::string *), (const, override));
+    MOCK_METHOD(hal::Error, getRequests,
+                (hal::DisplayRequest *, (std::unordered_map<Layer *, hal::LayerRequest> *)),
+                (override));
+    MOCK_METHOD(hal::Error, getConnectionType, (ui::DisplayConnectionType *), (const, override));
+    MOCK_METHOD(hal::Error, supportsDoze, (bool *), (const, override));
+    MOCK_METHOD(hal::Error, getHdrCapabilities, (android::HdrCapabilities *), (const, override));
+    MOCK_METHOD(hal::Error, getDisplayedContentSamplingAttributes,
+                (hal::PixelFormat *, hal::Dataspace *, uint8_t *), (const, override));
+    MOCK_METHOD(hal::Error, setDisplayContentSamplingEnabled, (bool, uint8_t, uint64_t),
+                (const, override));
+    MOCK_METHOD(hal::Error, getDisplayedContentSample,
+                (uint64_t, uint64_t, android::DisplayedFrameStats *), (const, override));
+    MOCK_METHOD(hal::Error, getReleaseFences,
+                ((std::unordered_map<Layer *, android::sp<android::Fence>> *)), (const, override));
+    MOCK_METHOD(hal::Error, present, (android::sp<android::Fence> *), (override));
+    MOCK_METHOD(hal::Error, setClientTarget,
+                (uint32_t, const android::sp<android::GraphicBuffer> &,
+                 const android::sp<android::Fence> &, hal::Dataspace),
+                (override));
+    MOCK_METHOD(hal::Error, setColorMode, (hal::ColorMode, hal::RenderIntent), (override));
+    MOCK_METHOD(hal::Error, setColorTransform, (const android::mat4 &, hal::ColorTransform),
+                (override));
+    MOCK_METHOD(hal::Error, setOutputBuffer,
+                (const android::sp<android::GraphicBuffer> &, const android::sp<android::Fence> &),
+                (override));
+    MOCK_METHOD(hal::Error, setPowerMode, (hal::PowerMode), (override));
+    MOCK_METHOD(hal::Error, setVsyncEnabled, (hal::Vsync), (override));
+    MOCK_METHOD(hal::Error, validate, (uint32_t *, uint32_t *), (override));
+    MOCK_METHOD(hal::Error, presentOrValidate,
+                (uint32_t *, uint32_t *, android::sp<android::Fence> *, uint32_t *), (override));
+    MOCK_METHOD(std::future<hal::Error>, setDisplayBrightness, (float), (override));
+    MOCK_METHOD(hal::Error, setActiveConfigWithConstraints,
+                (hal::HWConfigId, const hal::VsyncPeriodChangeConstraints &,
+                 hal::VsyncPeriodChangeTimeline *),
+                (override));
+    MOCK_METHOD(hal::Error, setAutoLowLatencyMode, (bool), (override));
+    MOCK_METHOD(hal::Error, getSupportedContentTypes, (std::vector<hal::ContentType> *),
+                (const, override));
+    MOCK_METHOD(hal::Error, setContentType, (hal::ContentType), (override));
+    MOCK_METHOD(hal::Error, getClientTargetProperty, (hal::ClientTargetProperty *), (override));
+};
+
+class Layer : public HWC2::Layer {
+public:
+    Layer();
+    ~Layer() override;
+
+    MOCK_METHOD(hal::HWLayerId, getId, (), (const, override));
+    MOCK_METHOD(hal::Error, setCursorPosition, (int32_t, int32_t), (override));
+    MOCK_METHOD(hal::Error, setBuffer,
+                (uint32_t, const android::sp<android::GraphicBuffer> &,
+                 const android::sp<android::Fence> &),
+                (override));
+    MOCK_METHOD(hal::Error, setSurfaceDamage, (const android::Region &), (override));
+    MOCK_METHOD(hal::Error, setBlendMode, (hal::BlendMode), (override));
+    MOCK_METHOD(hal::Error, setColor, (hal::Color), (override));
+    MOCK_METHOD(hal::Error, setCompositionType, (hal::Composition), (override));
+    MOCK_METHOD(hal::Error, setDataspace, (android::ui::Dataspace), (override));
+    MOCK_METHOD(hal::Error, setPerFrameMetadata, (const int32_t, const android::HdrMetadata &),
+                (override));
+    MOCK_METHOD(hal::Error, setDisplayFrame, (const android::Rect &), (override));
+    MOCK_METHOD(hal::Error, setPlaneAlpha, (float), (override));
+    MOCK_METHOD(hal::Error, setSidebandStream, (const native_handle_t *), (override));
+    MOCK_METHOD(hal::Error, setSourceCrop, (const android::FloatRect &), (override));
+    MOCK_METHOD(hal::Error, setTransform, (hal::Transform), (override));
+    MOCK_METHOD(hal::Error, setVisibleRegion, (const android::Region &), (override));
+    MOCK_METHOD(hal::Error, setZOrder, (uint32_t), (override));
+    MOCK_METHOD(hal::Error, setColorTransform, (const android::mat4 &), (override));
+    MOCK_METHOD(hal::Error, setLayerGenericMetadata,
+                (const std::string &, bool, const std::vector<uint8_t> &), (override));
+};
+
+} // namespace android::HWC2::mock