Merge "Use a strongly typed Uid in WindowInfo" into udc-qpr-dev
diff --git a/Android.bp b/Android.bp
index 3992f82..7f1ef67 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,14 @@
     ],
 }
 
+cc_library_headers {
+    name: "native_headers",
+    host_supported: true,
+    export_include_dirs: [
+        "include/",
+    ],
+}
+
 ndk_headers {
     name: "libandroid_headers",
     from: "include/android",
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 5dbf7ac..8a33756 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -3586,7 +3586,7 @@
         // an app; they are irrelevant here because bugreport is triggered via command line.
         // Update Last ID before calling Run().
         Initialize();
-        status = Run(-1 /* calling_uid */, "" /* calling_package */);
+        status = Run(0 /* calling_uid */, "" /* calling_package */);
     }
     return status;
 }
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index 1b7acab..c86993c 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -165,7 +165,8 @@
 
     // Bind mount necessary directories.
     constexpr const char* kBindMounts[] = {
-            "/data", "/dev", "/proc", "/sys"
+            "/data", "/dev", "/proc", "/sys",
+            "/sys/fs/selinux" /* Required for apexd which includes libselinux */
     };
     for (size_t i = 0; i < arraysize(kBindMounts); ++i) {
         std::string trg = StringPrintf("/postinstall%s", kBindMounts[i]);
diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h
index 9dedd2b..909bf08 100644
--- a/include/input/InputEventLabels.h
+++ b/include/input/InputEventLabels.h
@@ -40,7 +40,16 @@
 //   then you must add it to InputEventLabels.cpp.
 
 class InputEventLookup {
+    /**
+     * This class is not purely static, but uses a singleton pattern in order to delay the
+     * initialization of the maps that it contains. If it were purely static, the maps could be
+     * created early, and would cause sanitizers to report memory leaks.
+     */
 public:
+    InputEventLookup(InputEventLookup& other) = delete;
+
+    void operator=(const InputEventLookup&) = delete;
+
     static std::optional<int> lookupValueByLabel(const std::unordered_map<std::string, int>& map,
                                                  const char* literal);
 
@@ -61,17 +70,24 @@
     static EvdevEventLabel getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value);
 
 private:
-    static const std::unordered_map<std::string, int> KEYCODES;
+    InputEventLookup();
 
-    static const std::vector<InputEventLabel> KEY_NAMES;
+    static const InputEventLookup& get() {
+        static InputEventLookup sLookup;
+        return sLookup;
+    }
 
-    static const std::unordered_map<std::string, int> AXES;
+    const std::unordered_map<std::string, int> KEYCODES;
 
-    static const std::vector<InputEventLabel> AXES_NAMES;
+    const std::vector<InputEventLabel> KEY_NAMES;
 
-    static const std::unordered_map<std::string, int> LEDS;
+    const std::unordered_map<std::string, int> AXES;
 
-    static const std::unordered_map<std::string, int> FLAGS;
+    const std::vector<InputEventLabel> AXES_NAMES;
+
+    const std::unordered_map<std::string, int> LEDS;
+
+    const std::unordered_map<std::string, int> FLAGS;
 };
 
 } // namespace android
diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h
index d4589f5..3715408 100644
--- a/include/input/InputVerifier.h
+++ b/include/input/InputVerifier.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,13 +16,25 @@
 
 #pragma once
 
+#include <android-base/result.h>
 #include <input/Input.h>
-#include <map>
+#include "rust/cxx.h"
 
 namespace android {
 
+namespace input {
+namespace verifier {
+struct InputVerifier;
+}
+} // namespace input
+
 /*
  * Crash if the provided touch stream is inconsistent.
+ * This class is a pass-through to the rust implementation of InputVerifier.
+ * The rust class could also be used directly, but it would be less convenient.
+ * We can't directly invoke the rust methods on a rust object. So, there's no way to do:
+ * mVerifier.process_movement(...).
+ * This C++ class makes it a bit easier to use.
  *
  * TODO(b/211379801): Add support for hover events:
  * - No hover move without enter
@@ -34,16 +46,13 @@
 public:
     InputVerifier(const std::string& name);
 
-    void processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
-                         const PointerProperties* pointerProperties,
-                         const PointerCoords* pointerCoords, int32_t flags);
+    android::base::Result<void> processMovement(int32_t deviceId, int32_t action,
+                                                uint32_t pointerCount,
+                                                const PointerProperties* pointerProperties,
+                                                const PointerCoords* pointerCoords, int32_t flags);
 
 private:
-    const std::string mName;
-    std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTER_ID + 1>> mTouchingPointerIdsByDevice;
-    void ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount,
-                                     const PointerProperties* pointerProperties,
-                                     const char* action) const;
+    rust::Box<android::input::verifier::InputVerifier> mVerifier;
 };
 
 } // namespace android
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 5fbae3c..582437f 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -142,8 +142,8 @@
     return appDebuggable || platformDebuggable;
 }
 
-void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string path,
-                                                 const std::string sphalLibraries) {
+void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string& path,
+                                                 const std::string& sphalLibraries) {
     if (!mDriverPath.empty() || !mSphalLibraries.empty()) {
         ALOGV("ignoring attempt to change driver path from '%s' to '%s' or change sphal libraries "
               "from '%s' to '%s'",
@@ -392,55 +392,24 @@
     return true;
 }
 
-void* GraphicsEnv::loadLibrary(std::string name) {
-    const android_dlextinfo dlextinfo = {
-            .flags = ANDROID_DLEXT_USE_NAMESPACE,
-            .library_namespace = getAngleNamespace(),
-    };
-
-    std::string libName = std::string("lib") + name + "_angle.so";
-
-    void* so = android_dlopen_ext(libName.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
-
-    if (so) {
-        ALOGD("dlopen_ext from APK (%s) success at %p", libName.c_str(), so);
-        return so;
-    } else {
-        ALOGE("dlopen_ext(\"%s\") failed: %s", libName.c_str(), dlerror());
-    }
-
-    return nullptr;
-}
-
-bool GraphicsEnv::shouldUseAngle(std::string appName) {
-    if (appName != mAngleAppName) {
-        // Make sure we are checking the app we were init'ed for
-        ALOGE("App name does not match: expected '%s', got '%s'", mAngleAppName.c_str(),
-              appName.c_str());
-        return false;
-    }
-
-    return shouldUseAngle();
-}
-
 bool GraphicsEnv::shouldUseAngle() {
     // Make sure we are init'ed
-    if (mAngleAppName.empty()) {
-        ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE.");
+    if (mPackageName.empty()) {
+        ALOGV("Package name is empty. setAngleInfo() has not been called to enable ANGLE.");
         return false;
     }
 
-    return (mUseAngle == YES) ? true : false;
+    return (mShouldUseAngle == YES) ? true : false;
 }
 
-void GraphicsEnv::updateUseAngle() {
+void GraphicsEnv::updateShouldUseAngle() {
     const char* ANGLE_PREFER_ANGLE = "angle";
     const char* ANGLE_PREFER_NATIVE = "native";
 
-    mUseAngle = NO;
+    mShouldUseAngle = NO;
     if (mAngleDeveloperOptIn == ANGLE_PREFER_ANGLE) {
         ALOGV("User set \"Developer Options\" to force the use of ANGLE");
-        mUseAngle = YES;
+        mShouldUseAngle = YES;
     } else if (mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) {
         ALOGV("User set \"Developer Options\" to force the use of Native");
     } else {
@@ -448,13 +417,13 @@
     }
 }
 
-void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName,
-                               const std::string developerOptIn,
+void GraphicsEnv::setAngleInfo(const std::string& path, const std::string& packageName,
+                               const std::string& developerOptIn,
                                const std::vector<std::string> eglFeatures) {
-    if (mUseAngle != UNKNOWN) {
+    if (mShouldUseAngle != UNKNOWN) {
         // We've already figured out an answer for this app, so just return.
-        ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", appName.c_str(),
-              (mUseAngle == YES) ? "true" : "false");
+        ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", packageName.c_str(),
+              (mShouldUseAngle == YES) ? "true" : "false");
         return;
     }
 
@@ -462,16 +431,17 @@
 
     ALOGV("setting ANGLE path to '%s'", path.c_str());
     mAnglePath = path;
-    ALOGV("setting ANGLE app name to '%s'", appName.c_str());
-    mAngleAppName = appName;
+    ALOGV("setting app package name to '%s'", packageName.c_str());
+    mPackageName = packageName;
     ALOGV("setting ANGLE application opt-in to '%s'", developerOptIn.c_str());
     mAngleDeveloperOptIn = developerOptIn;
 
     // Update the current status of whether we should use ANGLE or not
-    updateUseAngle();
+    updateShouldUseAngle();
 }
 
-void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths) {
+void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace,
+                                const std::string& layerPaths) {
     if (mLayerPaths.empty()) {
         mLayerPaths = layerPaths;
         mAppNamespace = appNamespace;
@@ -485,8 +455,8 @@
     return mAppNamespace;
 }
 
-std::string& GraphicsEnv::getAngleAppName() {
-    return mAngleAppName;
+std::string& GraphicsEnv::getPackageName() {
+    return mPackageName;
 }
 
 const std::vector<std::string>& GraphicsEnv::getAngleEglFeatures() {
@@ -505,11 +475,11 @@
     return mDebugLayersGLES;
 }
 
-void GraphicsEnv::setDebugLayers(const std::string layers) {
+void GraphicsEnv::setDebugLayers(const std::string& layers) {
     mDebugLayers = layers;
 }
 
-void GraphicsEnv::setDebugLayersGLES(const std::string layers) {
+void GraphicsEnv::setDebugLayersGLES(const std::string& layers) {
     mDebugLayersGLES = layers;
 }
 
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index f9b234a..a1b5e50 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -55,7 +55,7 @@
     // Also set additional required sphal libraries to the linker for loading
     // graphics drivers. The string is a list of libraries separated by ':',
     // which is required by android_link_namespaces.
-    void setDriverPathAndSphalLibraries(const std::string path, const std::string sphalLibraries);
+    void setDriverPathAndSphalLibraries(const std::string& path, const std::string& sphalLibraries);
     // Get the updatable driver namespace.
     android_namespace_t* getDriverNamespace();
     std::string getDriverPath() const;
@@ -96,8 +96,6 @@
     /*
      * Apis for ANGLE
      */
-    // Check if the requested app should use ANGLE.
-    bool shouldUseAngle(std::string appName);
     // Check if this app process should use ANGLE.
     bool shouldUseAngle();
     // Set a search path for loading ANGLE libraries. The path is a list of
@@ -105,42 +103,39 @@
     // (libraries must be stored uncompressed and page aligned); such elements
     // in the search path must have a '!' after the zip filename, e.g.
     //     /system/app/ANGLEPrebuilt/ANGLEPrebuilt.apk!/lib/arm64-v8a
-    void setAngleInfo(const std::string path, const std::string appName, std::string devOptIn,
-                      const std::vector<std::string> eglFeatures);
+    void setAngleInfo(const std::string& path, const std::string& packageName,
+                      const std::string& devOptIn, const std::vector<std::string> eglFeatures);
     // Get the ANGLE driver namespace.
     android_namespace_t* getAngleNamespace();
-    // Get the app name for ANGLE debug message.
-    std::string& getAngleAppName();
-
+    // Get the app package name.
+    std::string& getPackageName();
     const std::vector<std::string>& getAngleEglFeatures();
+    // Set the persist.graphics.egl system property value.
+    void nativeToggleAngleAsSystemDriver(bool enabled);
 
     /*
      * Apis for debug layer
      */
     // Set additional layer search paths.
-    void setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths);
+    void setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string& layerPaths);
     // Get the app namespace for loading layers.
     NativeLoaderNamespace* getAppNamespace();
     // Get additional layer search paths.
     const std::string& getLayerPaths();
     // Set the Vulkan debug layers.
-    void setDebugLayers(const std::string layers);
+    void setDebugLayers(const std::string& layers);
     // Set the GL debug layers.
-    void setDebugLayersGLES(const std::string layers);
+    void setDebugLayersGLES(const std::string& layers);
     // Get the debug layers to load.
     const std::string& getDebugLayers();
     // Get the debug layers to load.
     const std::string& getDebugLayersGLES();
-    // Set the persist.graphics.egl system property value.
-    void nativeToggleAngleAsSystemDriver(bool enabled);
 
 private:
     enum UseAngle { UNKNOWN, YES, NO };
 
-    // Load requested ANGLE library.
-    void* loadLibrary(std::string name);
     // Update whether ANGLE should be used.
-    void updateUseAngle();
+    void updateShouldUseAngle();
     // Link updatable driver namespace with llndk and vndk-sp libs.
     bool linkDriverNamespaceLocked(android_namespace_t* vndkNamespace);
     // Check whether this process is ready to send stats.
@@ -149,39 +144,56 @@
     void sendGpuStatsLocked(GpuStatsInfo::Api api, bool isDriverLoaded, int64_t driverLoadingTime);
 
     GraphicsEnv() = default;
+
+    // This mutex protects the namespace creation.
+    std::mutex mNamespaceMutex;
+
+    /**
+     * Updatable driver variables.
+     */
     // Path to updatable driver libs.
     std::string mDriverPath;
     // Path to additional sphal libs linked to updatable driver namespace.
     std::string mSphalLibraries;
+    // Updatable driver namespace.
+    android_namespace_t* mDriverNamespace = nullptr;
+
+    /**
+     * ANGLE variables.
+     */
+    // Path to ANGLE libs.
+    std::string mAnglePath;
+    // App's package name.
+    std::string mPackageName;
+    // ANGLE developer opt in status.
+    std::string mAngleDeveloperOptIn;
+    // ANGLE EGL features;
+    std::vector<std::string> mAngleEglFeatures;
+    // Use ANGLE flag.
+    UseAngle mShouldUseAngle = UNKNOWN;
+    // ANGLE namespace.
+    android_namespace_t* mAngleNamespace = nullptr;
+
+    /**
+     * GPU metrics.
+     */
     // This mutex protects mGpuStats and get gpuservice call.
     std::mutex mStatsLock;
     // Cache the activity launch info
     bool mActivityLaunched = false;
     // Information bookkept for GpuStats.
     GpuStatsInfo mGpuStats;
-    // Path to ANGLE libs.
-    std::string mAnglePath;
-    // This App's name.
-    std::string mAngleAppName;
-    // ANGLE developer opt in status.
-    std::string mAngleDeveloperOptIn;
-    // ANGLE EGL features;
-    std::vector<std::string> mAngleEglFeatures;
-    // Use ANGLE flag.
-    UseAngle mUseAngle = UNKNOWN;
+
+    /**
+     * Debug layers.
+     */
     // Vulkan debug layers libs.
     std::string mDebugLayers;
     // GL debug layers libs.
     std::string mDebugLayersGLES;
     // Additional debug layers search path.
     std::string mLayerPaths;
-    // This mutex protects the namespace creation.
-    std::mutex mNamespaceMutex;
-    // Updatable driver namespace.
-    android_namespace_t* mDriverNamespace = nullptr;
-    // ANGLE namespace.
-    android_namespace_t* mAngleNamespace = nullptr;
-    // This App's namespace.
+    // This App's namespace to open native libraries.
     NativeLoaderNamespace* mAppNamespace = nullptr;
 };
 
diff --git a/libs/gui/TEST_MAPPING b/libs/gui/TEST_MAPPING
index 9415035..a4d9e77 100644
--- a/libs/gui/TEST_MAPPING
+++ b/libs/gui/TEST_MAPPING
@@ -4,10 +4,58 @@
       "path": "frameworks/native/libs/nativewindow"
     }
   ],
-  "postsubmit": [
+  "presubmit": [
     {
-      // TODO(257123981): move this to presubmit after dealing with existing breakages.
-      "name": "libgui_test"
+      "name": "libgui_test",
+      "options": [
+        // TODO(b/277604286): Failing on Cuttlefish.
+        {
+          "exclude-filter": "MultiTextureConsumerTest#EGLImageTargetWorks"
+        },
+
+        // TODO(b/285011590): Failing on Cuttlefish.
+        {
+          "exclude-filter": "SurfaceTest#GetHdrSupport"
+        },
+        {
+          "exclude-filter": "SurfaceTest#GetWideColorSupport"
+        },
+
+        // TODO(b/285006554): Failing on Cuttlefish.
+        {
+          "exclude-filter": "SurfaceTextureGLTest#InvalidWidthOrHeightFails"
+        },
+
+        // TODO(b/277347351): Known test data issues, failing across devices.
+        {
+          "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferNpot"
+        },
+        {
+          "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferPow2"
+        },
+        {
+          "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferWithCrop"
+        },
+        {
+          "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BuffersRepeatedly"
+        },
+
+        // TODO(b/285041169): Hanging on Cuttlefish.
+        {
+          "exclude-filter": "SurfaceTextureGLThreadToGLTest#UpdateTexImageBeforeFrameFinishedCompletes"
+        },
+        {
+          "exclude-filter": "SurfaceTextureGLThreadToGLTest#RepeatedUpdateTexImageBeforeFrameFinishedCompletes"
+        },
+        {
+          "exclude-filter": "SurfaceTextureGLThreadToGLTest#RepeatedUpdateTexImageAfterFrameFinishedCompletes"
+        },
+
+        // TODO(b/285041070): Failing on Cuttlefish.
+        {
+          "exclude-filter": "SurfaceTextureGLToGLTest#EglDestroySurfaceUnrefsBuffers"
+        }
+      ]
     }
   ]
 }
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index cd35d2f..462ce6e 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -21,6 +21,7 @@
     ],
 
     srcs: [
+        "LibGuiMain.cpp", // Custom gtest entrypoint
         "BLASTBufferQueue_test.cpp",
         "BufferItemConsumer_test.cpp",
         "BufferQueue_test.cpp",
diff --git a/libs/gui/tests/AndroidTest.xml b/libs/gui/tests/AndroidTest.xml
index 5e09fff..31b10d7 100644
--- a/libs/gui/tests/AndroidTest.xml
+++ b/libs/gui/tests/AndroidTest.xml
@@ -23,6 +23,7 @@
       <option name="screen-always-on" value="on" />
     </target_preparer>
     <option name="test-suite-tag" value="apct" />
+    <option name="not-shardable" value="true" />
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
         <option name="module-name" value="libgui_test" />
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index a3ad680..cd90168 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -176,18 +176,6 @@
 class BLASTBufferQueueTest : public ::testing::Test {
 public:
 protected:
-    BLASTBufferQueueTest() {
-        const ::testing::TestInfo* const testInfo =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name());
-    }
-
-    ~BLASTBufferQueueTest() {
-        const ::testing::TestInfo* const testInfo =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("End test:   %s.%s", testInfo->test_case_name(), testInfo->name());
-    }
-
     void SetUp() {
         mComposer = ComposerService::getComposerService();
         mClient = new SurfaceComposerClient();
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 2f1fd3e..d585881 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "BufferQueue_test"
 //#define LOG_NDEBUG 0
 
+#include "Constants.h"
 #include "MockConsumer.h"
 
 #include <gui/BufferItem.h>
@@ -46,20 +47,6 @@
 
 public:
 protected:
-    BufferQueueTest() {
-        const ::testing::TestInfo* const testInfo =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
-                testInfo->name());
-    }
-
-    ~BufferQueueTest() {
-        const ::testing::TestInfo* const testInfo =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("End test:   %s.%s", testInfo->test_case_name(),
-                testInfo->name());
-    }
-
     void GetMinUndequeuedBufferCount(int* bufferCount) {
         ASSERT_TRUE(bufferCount != nullptr);
         ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
@@ -535,7 +522,8 @@
     int slot;
     sp<Fence> fence;
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
-              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
 
     sp<GraphicBuffer> buffer;
     ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
@@ -578,7 +566,8 @@
     sp<Fence> fence;
     sp<GraphicBuffer> buffer;
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
-              mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+              mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                       nullptr, nullptr));
     ASSERT_EQ(OK, mProducer->requestBuffer(sharedSlot, &buffer));
 
     // Queue the buffer
@@ -592,7 +581,9 @@
     // always the same one and because async mode gets enabled.
     int slot;
     for (int i = 0; i < 5; i++) {
-        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+        ASSERT_EQ(OK,
+                  mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                           nullptr, nullptr));
         ASSERT_EQ(sharedSlot, slot);
         ASSERT_EQ(OK, mProducer->queueBuffer(sharedSlot, input, &output));
     }
@@ -629,7 +620,8 @@
     sp<Fence> fence;
     sp<GraphicBuffer> buffer;
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
-              mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+              mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                       nullptr, nullptr));
     ASSERT_EQ(OK, mProducer->requestBuffer(sharedSlot, &buffer));
 
     // Queue the buffer
@@ -656,7 +648,9 @@
     // always return the same one.
     int slot;
     for (int i = 0; i < 5; i++) {
-        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+        ASSERT_EQ(OK,
+                  mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                           nullptr, nullptr));
         ASSERT_EQ(sharedSlot, slot);
         ASSERT_EQ(OK, mProducer->queueBuffer(sharedSlot, input, &output));
     }
@@ -695,7 +689,8 @@
     sp<Fence> fence;
     sp<GraphicBuffer> buffer;
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
-              mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+              mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                       nullptr, nullptr));
     ASSERT_EQ(OK, mProducer->requestBuffer(sharedSlot, &buffer));
 
     // Enable shared buffer mode
@@ -712,7 +707,9 @@
     // always the same one and because async mode gets enabled.
     int slot;
     for (int i = 0; i < 5; i++) {
-        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+        ASSERT_EQ(OK,
+                  mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                           nullptr, nullptr));
         ASSERT_EQ(sharedSlot, slot);
         ASSERT_EQ(OK, mProducer->queueBuffer(sharedSlot, input, &output));
     }
@@ -747,7 +744,8 @@
     for (int i = 0; i < 5; ++i) {
         int slot = BufferQueue::INVALID_BUFFER_SLOT;
         sp<Fence> fence = Fence::NO_FENCE;
-        auto result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr);
+        auto result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                               nullptr, nullptr);
         if (i < 2) {
             ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
                     result);
@@ -774,7 +772,9 @@
     for (int i = 0; i < 2; ++i) {
         int slot = BufferQueue::INVALID_BUFFER_SLOT;
         sp<Fence> fence = Fence::NO_FENCE;
-        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+        ASSERT_EQ(OK,
+                  mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                           nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
         IGraphicBufferProducer::QueueBufferInput input(0ull, true,
                 HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT,
@@ -785,7 +785,9 @@
     int slot = BufferQueue::INVALID_BUFFER_SLOT;
     sp<Fence> fence = Fence::NO_FENCE;
     auto startTime = systemTime();
-    ASSERT_EQ(TIMED_OUT, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(TIMED_OUT,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_GE(systemTime() - startTime, TIMEOUT);
 
     // We're technically attaching the same buffer multiple times (since we
@@ -806,7 +808,8 @@
     int slot = BufferQueue::INVALID_BUFFER_SLOT;
     sp<Fence> sourceFence;
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
-              mProducer->dequeueBuffer(&slot, &sourceFence, 0, 0, 0, 0, nullptr, nullptr));
+              mProducer->dequeueBuffer(&slot, &sourceFence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                       nullptr, nullptr));
     sp<GraphicBuffer> buffer;
     ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
     ASSERT_EQ(OK, mProducer->detachBuffer(slot));
@@ -829,7 +832,8 @@
     int slot = BufferQueue::INVALID_BUFFER_SLOT;
     sp<Fence> fence;
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
-              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     sp<GraphicBuffer> firstBuffer;
     ASSERT_EQ(OK, mProducer->requestBuffer(slot, &firstBuffer));
 
@@ -841,7 +845,8 @@
     // Dequeue a second buffer
     slot = BufferQueue::INVALID_BUFFER_SLOT;
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
-              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     sp<GraphicBuffer> secondBuffer;
     ASSERT_EQ(OK, mProducer->requestBuffer(slot, &secondBuffer));
 
@@ -892,8 +897,8 @@
     int slots[3] = {};
     mProducer->setMaxDequeuedBufferCount(3);
     for (size_t i = 0; i < 3; ++i) {
-        status_t result =
-                mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0, 0, nullptr, nullptr);
+        status_t result = mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0,
+                                                   TEST_PRODUCER_USAGE_BITS, nullptr, nullptr);
         ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
         ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
     }
@@ -906,7 +911,9 @@
     // The first segment is a two-buffer segment, so we only put one buffer into
     // the queue at a time
     for (size_t i = 0; i < 5; ++i) {
-        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+        ASSERT_EQ(OK,
+                  mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                           nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
         ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
@@ -921,16 +928,22 @@
     // two-buffer segment, but then at the end, we put two buffers in the queue
     // at the same time before draining it.
     for (size_t i = 0; i < 5; ++i) {
-        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+        ASSERT_EQ(OK,
+                  mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                           nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
         ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
                 EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
         std::this_thread::sleep_for(16ms);
     }
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
     ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
@@ -945,10 +958,14 @@
 
     // The third segment is a triple-buffer segment, so the queue is switching
     // between one buffer and two buffers deep.
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
     for (size_t i = 0; i < 5; ++i) {
-        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+        ASSERT_EQ(OK,
+                  mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                           nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
         ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
@@ -1047,8 +1064,8 @@
     int slots[4] = {};
     mProducer->setMaxDequeuedBufferCount(4);
     for (size_t i = 0; i < 4; ++i) {
-        status_t result =
-                mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0, 0, nullptr, nullptr);
+        status_t result = mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0,
+                                                   TEST_PRODUCER_USAGE_BITS, nullptr, nullptr);
         ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
         ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
     }
@@ -1059,14 +1076,22 @@
     // Get buffers in all states: dequeued, filled, acquired, free
 
     // Fill 3 buffers
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
     // Dequeue 1 buffer
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
 
     // Acquire and free 1 buffer
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
@@ -1132,8 +1157,8 @@
     int slots[2] = {};
     ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(2));
     for (size_t i = 0; i < 2; ++i) {
-        status_t result =
-                mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0, 0, nullptr, nullptr);
+        status_t result = mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0,
+                                                   TEST_PRODUCER_USAGE_BITS, nullptr, nullptr);
         ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
         ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
     }
@@ -1143,10 +1168,14 @@
 
     // Fill 2 buffers without consumer consuming them. Verify that all
     // queued buffer returns proper bufferReplaced flag
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
     ASSERT_EQ(false, output.bufferReplaced);
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
     ASSERT_EQ(true, output.bufferReplaced);
 }
@@ -1167,7 +1196,8 @@
             NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
 
     // Dequeue, request, and queue one buffer
-    status_t result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr);
+    status_t result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+                                               nullptr, nullptr);
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
     ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
@@ -1182,7 +1212,9 @@
             EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
 
     // Dequeue and queue the buffer again
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
 
     // Acquire and release the buffer again. Upon acquiring, the buffer handle
@@ -1194,7 +1226,9 @@
             EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
 
     // Dequeue and queue the buffer again
-    ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+    ASSERT_EQ(OK,
+              mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+                                       nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
 
     // Disconnect the producer end. This should clear all of the slots and mark
diff --git a/libs/gui/tests/Constants.h b/libs/gui/tests/Constants.h
new file mode 100644
index 0000000..85c0f9f
--- /dev/null
+++ b/libs/gui/tests/Constants.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <hardware/gralloc.h>
+
+// Arbitrary non-zero usage flag.
+constexpr uint64_t TEST_PRODUCER_USAGE_BITS = GRALLOC_USAGE_SW_READ_RARELY;
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
index 0a14afa..d80bd9c 100644
--- a/libs/gui/tests/CpuConsumer_test.cpp
+++ b/libs/gui/tests/CpuConsumer_test.cpp
@@ -62,7 +62,7 @@
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
         CpuConsumerTestParams params = GetParam();
-        ALOGD("** Starting test %s (%d x %d, %d, 0x%x)",
+        ALOGD("** Starting parameterized test %s (%d x %d, %d, 0x%x)",
                 test_info->name(),
                 params.width, params.height,
                 params.maxLockedBuffers, params.format);
diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp
index 3ae4b6d..afeea42 100644
--- a/libs/gui/tests/GLTest.cpp
+++ b/libs/gui/tests/GLTest.cpp
@@ -29,10 +29,6 @@
 }
 
 void GLTest::SetUp() {
-    const ::testing::TestInfo* const testInfo =
-        ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name());
-
     mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
     ASSERT_EQ(EGL_SUCCESS, eglGetError());
     ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
@@ -132,10 +128,6 @@
         eglTerminate(mEglDisplay);
     }
     ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
-    const ::testing::TestInfo* const testInfo =
-        ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("End test:   %s.%s", testInfo->test_case_name(), testInfo->name());
 }
 
 EGLint const* GLTest::getConfigAttribs() {
diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp
index e6cb89c..b1f5d08 100644
--- a/libs/gui/tests/IGraphicBufferProducer_test.cpp
+++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "IGraphicBufferProducer_test"
 //#define LOG_NDEBUG 0
 
+#include "Constants.h"
 #include "MockConsumer.h"
 
 #include <gtest/gtest.h>
@@ -40,7 +41,6 @@
 #define TEST_API NATIVE_WINDOW_API_CPU
 #define TEST_API_OTHER NATIVE_WINDOW_API_EGL // valid API that's not TEST_API
 #define TEST_CONTROLLED_BY_APP false
-#define TEST_PRODUCER_USAGE_BITS (0)
 
 namespace android {
 
@@ -82,11 +82,6 @@
     IGraphicBufferProducerTest() {}
 
     virtual void SetUp() {
-        const ::testing::TestInfo* const testInfo =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
-                testInfo->name());
-
         mMC = new MockConsumer;
 
         switch (GetParam()) {
@@ -111,13 +106,6 @@
         ASSERT_OK(mConsumer->consumerConnect(mMC, /*controlledByApp*/ false));
     }
 
-    virtual void TearDown() {
-        const ::testing::TestInfo* const testInfo =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("End test:   %s.%s", testInfo->test_case_name(),
-                testInfo->name());
-    }
-
     status_t TryConnectProducer() {
         IGraphicBufferProducer::QueueBufferOutput output;
         return mProducer->connect(TEST_TOKEN,
diff --git a/libs/gui/tests/LibGuiMain.cpp b/libs/gui/tests/LibGuiMain.cpp
new file mode 100644
index 0000000..10f7207
--- /dev/null
+++ b/libs/gui/tests/LibGuiMain.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gtest/gtest.h"
+#include "log/log.h"
+
+namespace {
+
+class TestCaseLogger : public ::testing::EmptyTestEventListener {
+    void OnTestStart(const ::testing::TestInfo& testInfo) override {
+        ALOGD("Begin test: %s#%s", testInfo.test_suite_name(), testInfo.name());
+    }
+
+    void OnTestEnd(const testing::TestInfo& testInfo) override {
+        ALOGD("End test:   %s#%s", testInfo.test_suite_name(), testInfo.name());
+    }
+};
+
+} // namespace
+
+int main(int argc, char** argv) {
+    testing::InitGoogleTest(&argc, argv);
+    testing::UnitTest::GetInstance()->listeners().Append(new TestCaseLogger());
+    return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/libs/gui/tests/Malicious.cpp b/libs/gui/tests/Malicious.cpp
index 58d7cc6..376420c 100644
--- a/libs/gui/tests/Malicious.cpp
+++ b/libs/gui/tests/Malicious.cpp
@@ -151,7 +151,6 @@
     sp<MaliciousBQP> malicious = getMaliciousBQP();
     sp<Surface> surface = new Surface(malicious);
 
-    ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
     ANativeWindow_Buffer buffer;
     ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
     ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
@@ -165,7 +164,6 @@
     sp<MaliciousBQP> malicious = getMaliciousBQP();
     sp<Surface> surface = new Surface(malicious);
 
-    ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
     ANativeWindow_Buffer buffer;
     ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
     ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
@@ -179,7 +177,6 @@
     sp<MaliciousBQP> malicious = getMaliciousBQP();
     sp<Surface> surface = new Surface(malicious);
 
-    ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
     ANativeWindow_Buffer buffer;
     ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
     ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
@@ -193,7 +190,6 @@
     sp<MaliciousBQP> malicious = getMaliciousBQP();
     sp<Surface> surface = new Surface(malicious);
 
-    ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
     ANativeWindow_Buffer buffer;
     ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
     ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp
index 2f14924..f34b03e 100644
--- a/libs/gui/tests/StreamSplitter_test.cpp
+++ b/libs/gui/tests/StreamSplitter_test.cpp
@@ -30,23 +30,7 @@
 
 namespace android {
 
-class StreamSplitterTest : public ::testing::Test {
-
-protected:
-    StreamSplitterTest() {
-        const ::testing::TestInfo* const testInfo =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
-                testInfo->name());
-    }
-
-    ~StreamSplitterTest() {
-        const ::testing::TestInfo* const testInfo =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("End test:   %s.%s", testInfo->test_case_name(),
-                testInfo->name());
-    }
-};
+class StreamSplitterTest : public ::testing::Test {};
 
 struct FakeListener : public BnConsumerListener {
     virtual void onFrameAvailable(const BufferItem& /* item */) {}
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index 82b6697..b28dca8 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -40,11 +40,6 @@
     }
 
     virtual void SetUp() {
-        const ::testing::TestInfo* const testInfo =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("Begin test: %s.%s", testInfo->test_case_name(),
-                testInfo->name());
-
         sp<IGraphicBufferProducer> producer;
         sp<IGraphicBufferConsumer> consumer;
         BufferQueue::createBufferQueue(&producer, &consumer);
@@ -96,11 +91,6 @@
         eglDestroyContext(mEglDisplay, mEglContext);
         eglDestroySurface(mEglDisplay, mEglSurface);
         eglTerminate(mEglDisplay);
-
-        const ::testing::TestInfo* const testInfo =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("End test:   %s.%s", testInfo->test_case_name(),
-                testInfo->name());
     }
 
     virtual EGLint const* getConfigAttribs() {
diff --git a/libs/gui/tests/SurfaceTextureGL.h b/libs/gui/tests/SurfaceTextureGL.h
index 53eb68c..9d8af5d 100644
--- a/libs/gui/tests/SurfaceTextureGL.h
+++ b/libs/gui/tests/SurfaceTextureGL.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_SURFACE_TEXTURE_GL_H
 #define ANDROID_SURFACE_TEXTURE_GL_H
 
+#include "Constants.h"
 #include "GLTest.h"
 
 #include "FrameWaiter.h"
@@ -43,6 +44,7 @@
                 true, false);
         mSTC = new Surface(producer);
         mANW = mSTC;
+        ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), TEST_PRODUCER_USAGE_BITS));
         mTextureRenderer = new TextureRenderer(TEX_ID, mST);
         ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp());
         mFW = new FrameWaiter;
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 096a43c..90c0a63 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "Constants.h"
 #include "MockConsumer.h"
 
 #include <gtest/gtest.h>
@@ -148,6 +149,7 @@
                 /*listener*/listener));
         const int BUFFER_COUNT = 4 + extraDiscardedBuffers;
         ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+        ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
 
         ANativeWindowBuffer* buffers[BUFFER_COUNT];
         // Dequeue first to allocate a number of buffers
@@ -530,7 +532,8 @@
 
     ASSERT_EQ(NO_ERROR, native_window_api_connect(window.get(),
             NATIVE_WINDOW_API_CPU));
-    native_window_set_buffer_count(window.get(), 4);
+    ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), 4));
+    ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
 
     int fence;
     ANativeWindowBuffer* buffer;
@@ -560,6 +563,7 @@
             /*reportBufferRemoval*/true));
     const int BUFFER_COUNT = 4;
     ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+    ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
 
     sp<GraphicBuffer> detachedBuffer;
     sp<Fence> outFence;
@@ -1202,7 +1206,8 @@
 
         ASSERT_EQ(NO_ERROR, native_window_api_connect(mWindow.get(),
                 NATIVE_WINDOW_API_CPU));
-        native_window_set_buffer_count(mWindow.get(), 4);
+        ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(mWindow.get(), 4));
+        ASSERT_EQ(NO_ERROR, native_window_set_usage(mWindow.get(), TEST_PRODUCER_USAGE_BITS));
     }
 
     void disableFrameTimestamps() {
@@ -2068,8 +2073,9 @@
 
     sp<Surface> surface = new Surface(producer);
     sp<ANativeWindow> window(surface);
-    native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU);
-    native_window_set_buffers_dimensions(window.get(), 0, 0);
+    ASSERT_EQ(NO_ERROR, native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(NO_ERROR, native_window_set_buffers_dimensions(window.get(), 0, 0));
+    ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
 
     int fence;
     ANativeWindowBuffer* buffer;
@@ -2121,6 +2127,7 @@
     native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU);
     consumer->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_270);
     native_window_set_buffers_dimensions(window.get(), 0, 0);
+    native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS);
     ASSERT_EQ(NO_ERROR, window->dequeueBuffer(window.get(), &buffer, &fence));
     EXPECT_EQ(10, buffer->width);
     EXPECT_EQ(20, buffer->height);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 4be7328..80a8c8d 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -33,6 +33,138 @@
     ],
 }
 
+aidl_interface {
+    name: "inputconstants",
+    host_supported: true,
+    vendor_available: true,
+    unstable: true,
+    srcs: [
+        ":inputconstants_aidl",
+    ],
+
+    backend: {
+        rust: {
+            enabled: true,
+        },
+    },
+}
+
+rust_bindgen {
+    name: "libinput_bindgen",
+    host_supported: true,
+    crate_name: "input_bindgen",
+    visibility: ["//frameworks/native/services/inputflinger"],
+    wrapper_src: "InputWrapper.hpp",
+
+    include_dirs: [
+        "frameworks/native/include",
+    ],
+
+    source_stem: "bindings",
+
+    bindgen_flags: [
+        "--verbose",
+        "--allowlist-var=AMOTION_EVENT_FLAG_CANCELED",
+        "--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
+        "--allowlist-var=AMOTION_EVENT_ACTION_UP",
+        "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
+        "--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
+        "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
+        "--allowlist-var=MAX_POINTER_ID",
+    ],
+
+    static_libs: [
+        "inputconstants-cpp",
+        "libui-types",
+    ],
+    shared_libs: ["libc++"],
+    header_libs: [
+        "native_headers",
+        "jni_headers",
+        "flatbuffer_headers",
+    ],
+}
+
+// Contains methods to help access C++ code from rust
+cc_library_static {
+    name: "libinput_from_rust_to_cpp",
+    cpp_std: "c++20",
+    host_supported: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    srcs: [
+        "FromRustToCpp.cpp",
+    ],
+
+    generated_headers: [
+        "cxx-bridge-header",
+    ],
+    generated_sources: ["libinput_cxx_bridge_code"],
+
+    shared_libs: [
+        "libbase",
+    ],
+}
+
+genrule {
+    name: "libinput_cxx_bridge_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["input_verifier.rs"],
+    out: ["inputverifier_generated.cpp"],
+}
+
+genrule {
+    name: "libinput_cxx_bridge_header",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+    srcs: ["input_verifier.rs"],
+    out: ["input_verifier.rs.h"],
+}
+
+rust_defaults {
+    name: "libinput_rust_defaults",
+    srcs: ["input_verifier.rs"],
+    host_supported: true,
+    rustlibs: [
+        "libbitflags",
+        "libcxx",
+        "libinput_bindgen",
+        "liblogger",
+        "liblog_rust",
+        "inputconstants-rust",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+}
+
+rust_ffi_static {
+    name: "libinput_rust",
+    crate_name: "input",
+    defaults: ["libinput_rust_defaults"],
+}
+
+rust_test {
+    name: "libinput_rust_test",
+    defaults: ["libinput_rust_defaults"],
+    whole_static_libs: [
+        "libinput_from_rust_to_cpp",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    test_suites: ["device_tests"],
+    sanitize: {
+        hwaddress: true,
+    },
+}
+
 cc_library {
     name: "libinput",
     cpp_std: "c++20",
@@ -44,6 +176,7 @@
         "-Wno-unused-parameter",
     ],
     srcs: [
+        "FromRustToCpp.cpp",
         "Input.cpp",
         "InputDevice.cpp",
         "InputEventLabels.cpp",
@@ -70,9 +203,13 @@
     export_header_lib_headers: ["jni_headers"],
 
     generated_headers: [
+        "cxx-bridge-header",
+        "libinput_cxx_bridge_header",
         "toolbox_input_labels",
     ],
 
+    generated_sources: ["libinput_cxx_bridge_code"],
+
     shared_libs: [
         "libbase",
         "libcutils",
@@ -92,20 +229,29 @@
     },
 
     static_libs: [
+        "inputconstants-cpp",
         "libui-types",
         "libtflite_static",
     ],
 
+    whole_static_libs: [
+        "libinput_rust",
+    ],
+
     export_static_lib_headers: [
         "libui-types",
     ],
 
+    export_generated_headers: [
+        "cxx-bridge-header",
+        "libinput_cxx_bridge_header",
+    ],
+
     target: {
         android: {
             srcs: [
                 "InputTransport.cpp",
                 "android/os/IInputFlinger.aidl",
-                ":inputconstants_aidl",
             ],
 
             export_shared_lib_headers: ["libbinder"],
@@ -140,9 +286,6 @@
         host_linux: {
             srcs: [
                 "InputTransport.cpp",
-                "android/os/IInputConstants.aidl",
-                "android/os/IInputFlinger.aidl",
-                "android/os/InputConfig.aidl",
             ],
             static_libs: [
                 "libhostgraphics",
diff --git a/libs/input/FromRustToCpp.cpp b/libs/input/FromRustToCpp.cpp
new file mode 100644
index 0000000..e4ce62e
--- /dev/null
+++ b/libs/input/FromRustToCpp.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <ffi/FromRustToCpp.h>
+
+namespace android {
+
+bool shouldLog(rust::Str tag) {
+    return android::base::ShouldLog(android::base::LogSeverity::DEBUG, tag.data());
+}
+
+} // namespace android
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index 1c7cc12..bade686 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -434,17 +434,14 @@
 // clang-format on
 
 // --- InputEventLookup ---
-const std::unordered_map<std::string, int> InputEventLookup::KEYCODES = {KEYCODES_SEQUENCE};
 
-const std::vector<InputEventLabel> InputEventLookup::KEY_NAMES = {KEYCODES_SEQUENCE};
-
-const std::unordered_map<std::string, int> InputEventLookup::AXES = {AXES_SEQUENCE};
-
-const std::vector<InputEventLabel> InputEventLookup::AXES_NAMES = {AXES_SEQUENCE};
-
-const std::unordered_map<std::string, int> InputEventLookup::LEDS = {LEDS_SEQUENCE};
-
-const std::unordered_map<std::string, int> InputEventLookup::FLAGS = {FLAGS_SEQUENCE};
+InputEventLookup::InputEventLookup()
+      : KEYCODES({KEYCODES_SEQUENCE}),
+        KEY_NAMES({KEYCODES_SEQUENCE}),
+        AXES({AXES_SEQUENCE}),
+        AXES_NAMES({AXES_SEQUENCE}),
+        LEDS({LEDS_SEQUENCE}),
+        FLAGS({FLAGS_SEQUENCE}) {}
 
 std::optional<int> InputEventLookup::lookupValueByLabel(
         const std::unordered_map<std::string, int>& map, const char* literal) {
@@ -462,30 +459,36 @@
 }
 
 std::optional<int> InputEventLookup::getKeyCodeByLabel(const char* label) {
-    return lookupValueByLabel(KEYCODES, label);
+    const auto& self = get();
+    return self.lookupValueByLabel(self.KEYCODES, label);
 }
 
 const char* InputEventLookup::getLabelByKeyCode(int32_t keyCode) {
-    if (keyCode >= 0 && static_cast<size_t>(keyCode) < KEYCODES.size()) {
-        return lookupLabelByValue(KEY_NAMES, keyCode);
+    const auto& self = get();
+    if (keyCode >= 0 && static_cast<size_t>(keyCode) < self.KEYCODES.size()) {
+        return get().lookupLabelByValue(self.KEY_NAMES, keyCode);
     }
     return nullptr;
 }
 
 std::optional<int> InputEventLookup::getKeyFlagByLabel(const char* label) {
-    return lookupValueByLabel(FLAGS, label);
+    const auto& self = get();
+    return lookupValueByLabel(self.FLAGS, label);
 }
 
 std::optional<int> InputEventLookup::getAxisByLabel(const char* label) {
-    return lookupValueByLabel(AXES, label);
+    const auto& self = get();
+    return lookupValueByLabel(self.AXES, label);
 }
 
 const char* InputEventLookup::getAxisLabel(int32_t axisId) {
-    return lookupLabelByValue(AXES_NAMES, axisId);
+    const auto& self = get();
+    return lookupLabelByValue(self.AXES_NAMES, axisId);
 }
 
 std::optional<int> InputEventLookup::getLedByLabel(const char* label) {
-    return lookupValueByLabel(LEDS, label);
+    const auto& self = get();
+    return lookupValueByLabel(self.LEDS, label);
 }
 
 namespace {
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 4ecb641..4d3d8bc 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -14,6 +14,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <binder/Parcel.h>
@@ -81,6 +82,7 @@
 
 } // namespace
 
+using android::base::Result;
 using android::base::StringPrintf;
 
 namespace android {
@@ -621,8 +623,12 @@
         ATRACE_NAME(message.c_str());
     }
     if (verifyEvents()) {
-        mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties,
-                                       pointerCoords, flags);
+        Result<void> result =
+                mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties,
+                                               pointerCoords, flags);
+        if (!result.ok()) {
+            LOG(FATAL) << "Bad stream: " << result.error();
+        }
     }
     if (debugTransportPublisher()) {
         std::string transformString;
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
index eb75804..32b4ca0 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -18,111 +18,35 @@
 
 #include <android-base/logging.h>
 #include <input/InputVerifier.h>
+#include "input_verifier.rs.h"
+
+using android::base::Error;
+using android::base::Result;
+using android::input::RustPointerProperties;
 
 namespace android {
 
-/**
- * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead
- * to inconsistent events.
- * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG"
- */
-static bool logEvents() {
-    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "LogEvents", ANDROID_LOG_INFO);
-}
-
 // --- InputVerifier ---
 
-InputVerifier::InputVerifier(const std::string& name) : mName(name){};
+InputVerifier::InputVerifier(const std::string& name)
+      : mVerifier(android::input::verifier::create(name)){};
 
-void InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
-                                    const PointerProperties* pointerProperties,
-                                    const PointerCoords* pointerCoords, int32_t flags) {
-    if (logEvents()) {
-        LOG(ERROR) << "Processing " << MotionEvent::actionToString(action) << " for device "
-                   << deviceId << " (" << pointerCount << " pointer"
-                   << (pointerCount == 1 ? "" : "s") << ") on " << mName;
+Result<void> InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
+                                            const PointerProperties* pointerProperties,
+                                            const PointerCoords* pointerCoords, int32_t flags) {
+    std::vector<RustPointerProperties> rpp;
+    for (size_t i = 0; i < pointerCount; i++) {
+        rpp.emplace_back(RustPointerProperties{.id = pointerProperties[i].id});
     }
-
-    switch (MotionEvent::getActionMasked(action)) {
-        case AMOTION_EVENT_ACTION_DOWN: {
-            auto [it, inserted] = mTouchingPointerIdsByDevice.insert({deviceId, {}});
-            if (!inserted) {
-                LOG(FATAL) << "Got ACTION_DOWN, but already have touching pointers " << it->second
-                           << " for device " << deviceId << " on " << mName;
-            }
-            it->second.set(pointerProperties[0].id);
-            break;
-        }
-        case AMOTION_EVENT_ACTION_POINTER_DOWN: {
-            auto it = mTouchingPointerIdsByDevice.find(deviceId);
-            if (it == mTouchingPointerIdsByDevice.end()) {
-                LOG(FATAL) << "Got POINTER_DOWN, but no touching pointers for device " << deviceId
-                           << " on " << mName;
-            }
-            it->second.set(pointerProperties[MotionEvent::getActionIndex(action)].id);
-            break;
-        }
-        case AMOTION_EVENT_ACTION_MOVE: {
-            ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "MOVE");
-            break;
-        }
-        case AMOTION_EVENT_ACTION_POINTER_UP: {
-            auto it = mTouchingPointerIdsByDevice.find(deviceId);
-            if (it == mTouchingPointerIdsByDevice.end()) {
-                LOG(FATAL) << "Got POINTER_UP, but no touching pointers for device " << deviceId
-                           << " on " << mName;
-            }
-            it->second.reset(pointerProperties[MotionEvent::getActionIndex(action)].id);
-            break;
-        }
-        case AMOTION_EVENT_ACTION_UP: {
-            auto it = mTouchingPointerIdsByDevice.find(deviceId);
-            if (it == mTouchingPointerIdsByDevice.end()) {
-                LOG(FATAL) << "Got ACTION_UP, but no record for deviceId " << deviceId << " on "
-                           << mName;
-            }
-            const auto& [_, touchingPointerIds] = *it;
-            if (touchingPointerIds.count() != 1) {
-                LOG(FATAL) << "Got ACTION_UP, but we have pointers: " << touchingPointerIds
-                           << " for deviceId " << deviceId << " on " << mName;
-            }
-            const int32_t pointerId = pointerProperties[0].id;
-            if (!touchingPointerIds.test(pointerId)) {
-                LOG(FATAL) << "Got ACTION_UP, but pointerId " << pointerId
-                           << " is not touching. Touching pointers: " << touchingPointerIds
-                           << " for deviceId " << deviceId << " on " << mName;
-            }
-            mTouchingPointerIdsByDevice.erase(it);
-            break;
-        }
-        case AMOTION_EVENT_ACTION_CANCEL: {
-            if ((flags & AMOTION_EVENT_FLAG_CANCELED) != AMOTION_EVENT_FLAG_CANCELED) {
-                LOG(FATAL) << "For ACTION_CANCEL, must set FLAG_CANCELED";
-            }
-            ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "CANCEL");
-            mTouchingPointerIdsByDevice.erase(deviceId);
-            break;
-        }
+    rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
+    rust::String errorMessage =
+            android::input::verifier::process_movement(*mVerifier, deviceId, action, properties,
+                                                       flags);
+    if (errorMessage.empty()) {
+        return {};
+    } else {
+        return Error() << errorMessage;
     }
 }
 
-void InputVerifier::ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount,
-                                                const PointerProperties* pointerProperties,
-                                                const char* action) const {
-    auto it = mTouchingPointerIdsByDevice.find(deviceId);
-    if (it == mTouchingPointerIdsByDevice.end()) {
-        LOG(FATAL) << "Got " << action << ", but no touching pointers for device " << deviceId
-                   << " on " << mName;
-    }
-    const auto& [_, touchingPointerIds] = *it;
-    for (size_t i = 0; i < pointerCount; i++) {
-        const int32_t pointerId = pointerProperties[i].id;
-        if (!touchingPointerIds.test(pointerId)) {
-            LOG(FATAL) << "Got " << action << " for pointerId " << pointerId
-                       << " but the touching pointers are " << touchingPointerIds << " on "
-                       << mName;
-        }
-    }
-};
-
 } // namespace android
diff --git a/libs/input/InputWrapper.hpp b/libs/input/InputWrapper.hpp
new file mode 100644
index 0000000..a01080d
--- /dev/null
+++ b/libs/input/InputWrapper.hpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/input.h>
+#include "input/Input.h"
diff --git a/libs/input/ffi/FromRustToCpp.h b/libs/input/ffi/FromRustToCpp.h
new file mode 100644
index 0000000..889945c
--- /dev/null
+++ b/libs/input/ffi/FromRustToCpp.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "rust/cxx.h"
+
+namespace android {
+
+bool shouldLog(rust::Str tag);
+
+} // namespace android
diff --git a/libs/input/input_verifier.rs b/libs/input/input_verifier.rs
new file mode 100644
index 0000000..2e05a63
--- /dev/null
+++ b/libs/input/input_verifier.rs
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Validate the incoming motion stream.
+//! This class is not thread-safe.
+//! State is stored in the "InputVerifier" object
+//! that can be created via the 'create' method.
+//! Usage:
+//! Box<InputVerifier> verifier = create("inputChannel name");
+//! result = process_movement(verifier, ...);
+//! if (result) {
+//!    crash(result.error_message());
+//! }
+
+use std::collections::HashMap;
+use std::collections::HashSet;
+
+use bitflags::bitflags;
+use log::info;
+
+#[cxx::bridge(namespace = "android::input")]
+mod ffi {
+    #[namespace = "android"]
+    unsafe extern "C++" {
+        include!("ffi/FromRustToCpp.h");
+        fn shouldLog(tag: &str) -> bool;
+    }
+    #[namespace = "android::input::verifier"]
+    extern "Rust" {
+        type InputVerifier;
+
+        fn create(name: String) -> Box<InputVerifier>;
+        fn process_movement(
+            verifier: &mut InputVerifier,
+            device_id: i32,
+            action: u32,
+            pointer_properties: &[RustPointerProperties],
+            flags: i32,
+        ) -> String;
+    }
+
+    pub struct RustPointerProperties {
+        id: i32,
+    }
+}
+
+use crate::ffi::shouldLog;
+use crate::ffi::RustPointerProperties;
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+struct DeviceId(i32);
+
+fn process_movement(
+    verifier: &mut InputVerifier,
+    device_id: i32,
+    action: u32,
+    pointer_properties: &[RustPointerProperties],
+    flags: i32,
+) -> String {
+    let result = verifier.process_movement(
+        DeviceId(device_id),
+        action,
+        pointer_properties,
+        Flags::from_bits(flags).unwrap(),
+    );
+    match result {
+        Ok(()) => "".to_string(),
+        Err(e) => e,
+    }
+}
+
+fn create(name: String) -> Box<InputVerifier> {
+    Box::new(InputVerifier::new(&name))
+}
+
+#[repr(u32)]
+enum MotionAction {
+    Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+    Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
+    Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+    Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+    Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
+    PointerDown { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
+    PointerUp { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
+    HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+    HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+    HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+    Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
+    ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+    ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+}
+
+fn get_action_index(action: u32) -> usize {
+    let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+        >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+    index.try_into().unwrap()
+}
+
+impl From<u32> for MotionAction {
+    fn from(action: u32) -> Self {
+        let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
+        let action_index = get_action_index(action);
+        match action_masked {
+            input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
+            input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
+            input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
+            input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
+            input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
+            input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
+                MotionAction::PointerDown { action_index }
+            }
+            input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
+                MotionAction::PointerUp { action_index }
+            }
+            input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
+            input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
+            input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
+            input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
+            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
+            input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
+            _ => panic!("Unknown action: {}", action),
+        }
+    }
+}
+
+bitflags! {
+    struct Flags: i32 {
+        const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED;
+    }
+}
+
+fn motion_action_to_string(action: u32) -> String {
+    match action.into() {
+        MotionAction::Down => "DOWN".to_string(),
+        MotionAction::Up => "UP".to_string(),
+        MotionAction::Move => "MOVE".to_string(),
+        MotionAction::Cancel => "CANCEL".to_string(),
+        MotionAction::Outside => "OUTSIDE".to_string(),
+        MotionAction::PointerDown { action_index } => {
+            format!("POINTER_DOWN({})", action_index)
+        }
+        MotionAction::PointerUp { action_index } => {
+            format!("POINTER_UP({})", action_index)
+        }
+        MotionAction::HoverMove => "HOVER_MOVE".to_string(),
+        MotionAction::Scroll => "SCROLL".to_string(),
+        MotionAction::HoverEnter => "HOVER_ENTER".to_string(),
+        MotionAction::HoverExit => "HOVER_EXIT".to_string(),
+        MotionAction::ButtonPress => "BUTTON_PRESS".to_string(),
+        MotionAction::ButtonRelease => "BUTTON_RELEASE".to_string(),
+    }
+}
+
+/**
+ * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead
+ * to inconsistent events.
+ * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG"
+ */
+fn log_events() -> bool {
+    shouldLog("InputVerifierLogEvents")
+}
+
+struct InputVerifier {
+    name: String,
+    touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+}
+
+impl InputVerifier {
+    fn new(name: &str) -> Self {
+        logger::init(
+            logger::Config::default()
+                .with_tag_on_device("InputVerifier")
+                .with_min_level(log::Level::Trace),
+        );
+        Self { name: name.to_owned(), touching_pointer_ids_by_device: HashMap::new() }
+    }
+
+    fn process_movement(
+        &mut self,
+        device_id: DeviceId,
+        action: u32,
+        pointer_properties: &[RustPointerProperties],
+        flags: Flags,
+    ) -> Result<(), String> {
+        if log_events() {
+            info!(
+                "Processing {} for device {:?} ({} pointer{}) on {}",
+                motion_action_to_string(action),
+                device_id,
+                pointer_properties.len(),
+                if pointer_properties.len() == 1 { "" } else { "s" },
+                self.name
+            );
+        }
+
+        match action.into() {
+            MotionAction::Down => {
+                let it = self
+                    .touching_pointer_ids_by_device
+                    .entry(device_id)
+                    .or_insert_with(HashSet::new);
+                let pointer_id = pointer_properties[0].id;
+                if it.contains(&pointer_id) {
+                    return Err(format!(
+                        "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
+                        self.name, device_id, it
+                    ));
+                }
+                it.insert(pointer_id);
+            }
+            MotionAction::PointerDown { action_index } => {
+                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+                    return Err(format!(
+                        "{}: Received POINTER_DOWN but no pointers are currently down \
+                        for device {:?}",
+                        self.name, device_id
+                    ));
+                }
+                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                let pointer_id = pointer_properties[action_index].id;
+                if it.contains(&pointer_id) {
+                    return Err(format!(
+                        "{}: Pointer with id={} not found in the properties",
+                        self.name, pointer_id
+                    ));
+                }
+                it.insert(pointer_id);
+            }
+            MotionAction::Move => {
+                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+                    return Err(format!(
+                        "{}: ACTION_MOVE touching pointers don't match",
+                        self.name
+                    ));
+                }
+            }
+            MotionAction::PointerUp { action_index } => {
+                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+                    return Err(format!(
+                        "{}: Received POINTER_UP but no pointers are currently down for device \
+                        {:?}",
+                        self.name, device_id
+                    ));
+                }
+                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                let pointer_id = pointer_properties[action_index].id;
+                it.remove(&pointer_id);
+            }
+            MotionAction::Up => {
+                if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+                    return Err(format!(
+                        "{} Received ACTION_UP but no pointers are currently down for device {:?}",
+                        self.name, device_id
+                    ));
+                }
+                let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+                if it.len() != 1 {
+                    return Err(format!(
+                        "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
+                        self.name, it, device_id
+                    ));
+                }
+                let pointer_id = pointer_properties[0].id;
+                if !it.contains(&pointer_id) {
+                    return Err(format!(
+                        "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
+                        {:?} for device {:?}",
+                        self.name, pointer_id, it, device_id
+                    ));
+                }
+                it.clear();
+            }
+            MotionAction::Cancel => {
+                if flags.contains(Flags::CANCELED) {
+                    return Err(format!(
+                        "{}: For ACTION_CANCEL, must set FLAG_CANCELED",
+                        self.name
+                    ));
+                }
+                if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+                    return Err(format!(
+                        "{}: Got ACTION_CANCEL, but the pointers don't match. \
+                        Existing pointers: {:?}",
+                        self.name, self.touching_pointer_ids_by_device
+                    ));
+                }
+                self.touching_pointer_ids_by_device.remove(&device_id);
+            }
+            _ => return Ok(()),
+        }
+        Ok(())
+    }
+
+    fn ensure_touching_pointers_match(
+        &self,
+        device_id: DeviceId,
+        pointer_properties: &[RustPointerProperties],
+    ) -> bool {
+        let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
+            return false;
+        };
+
+        for pointer_property in pointer_properties.iter() {
+            let pointer_id = pointer_property.id;
+            if !pointers.contains(&pointer_id) {
+                return false;
+            }
+        }
+        true
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::DeviceId;
+    use crate::Flags;
+    use crate::InputVerifier;
+    use crate::RustPointerProperties;
+    #[test]
+    fn single_pointer_stream() {
+        let mut verifier = InputVerifier::new("Test");
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                Flags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                &pointer_properties,
+                Flags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                input_bindgen::AMOTION_EVENT_ACTION_UP,
+                &pointer_properties,
+                Flags::empty(),
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn multi_device_stream() {
+        let mut verifier = InputVerifier::new("Test");
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                Flags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                &pointer_properties,
+                Flags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(2),
+                input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+                &pointer_properties,
+                Flags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(2),
+                input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+                &pointer_properties,
+                Flags::empty(),
+            )
+            .is_ok());
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                input_bindgen::AMOTION_EVENT_ACTION_UP,
+                &pointer_properties,
+                Flags::empty(),
+            )
+            .is_ok());
+    }
+
+    #[test]
+    fn test_invalid_up() {
+        let mut verifier = InputVerifier::new("Test");
+        let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+        assert!(verifier
+            .process_movement(
+                DeviceId(1),
+                input_bindgen::AMOTION_EVENT_ACTION_UP,
+                &pointer_properties,
+                Flags::empty(),
+            )
+            .is_err());
+    }
+}
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 5854135..fda6ea1 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -86,6 +86,7 @@
 // Debugging settings
 static const bool kPrintLayerSettings = false;
 static const bool kFlushAfterEveryLayer = kPrintLayerSettings;
+static constexpr bool kEnableLayerBrightening = true;
 
 } // namespace
 
@@ -699,7 +700,8 @@
 
     // ...and compute the dimming ratio if dimming is requested
     const float displayDimmingRatio = display.targetLuminanceNits > 0.f &&
-                    maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint
+                    maxLayerWhitePoint > 0.f &&
+                    (kEnableLayerBrightening || display.targetLuminanceNits > maxLayerWhitePoint)
             ? maxLayerWhitePoint / display.targetLuminanceNits
             : 1.f;
 
diff --git a/libs/ui/include/ui/FenceTime.h b/libs/ui/include/ui/FenceTime.h
index ac75f43..334106f 100644
--- a/libs/ui/include/ui/FenceTime.h
+++ b/libs/ui/include/ui/FenceTime.h
@@ -142,6 +142,8 @@
     std::atomic<nsecs_t> mSignalTime{Fence::SIGNAL_TIME_INVALID};
 };
 
+using FenceTimePtr = std::shared_ptr<FenceTime>;
+
 // A queue of FenceTimes that are expected to signal in FIFO order.
 // Only maintains a queue of weak pointers so it doesn't keep references
 // to Fences on its own.
@@ -190,8 +192,15 @@
 // before the new one is added.
 class FenceToFenceTimeMap {
 public:
-    // Create a new FenceTime with that wraps the provided Fence.
-    std::shared_ptr<FenceTime> createFenceTimeForTest(const sp<Fence>& fence);
+    using FencePair = std::pair<sp<Fence>, FenceTimePtr>;
+
+    FencePair makePendingFenceForTest() {
+        const auto fence = sp<Fence>::make();
+        return {fence, createFenceTimeForTest(fence)};
+    }
+
+    // Create a new FenceTime that wraps the provided Fence.
+    FenceTimePtr createFenceTimeForTest(const sp<Fence>&);
 
     // Signals all FenceTimes created through this class that are wrappers
     // around |fence|.
@@ -205,7 +214,6 @@
     std::unordered_map<Fence*, std::vector<std::weak_ptr<FenceTime>>> mMap;
 };
 
-
-}; // namespace android
+} // namespace android
 
 #endif // ANDROID_FENCE_TIME_H
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index bb3b43a..b4fc5f0 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -147,13 +147,18 @@
         RO_BOARD_PLATFORM_PROPERTY,
 };
 
+// Check whether the loaded system drivers should be unloaded in order to
+// load ANGLE or the updatable graphics drivers.
+// If ANGLE namespace is set, it means the application is identified to run on top of ANGLE.
+// If updatable graphics driver namespace is set, it means the application is identified to
+// run on top of updatable graphics drivers.
 static bool should_unload_system_driver(egl_connection_t* cnx) {
     // Return false if the system driver has been unloaded once.
     if (cnx->systemDriverUnloaded) {
         return false;
     }
 
-    // Return true if Angle namespace is set.
+    // Return true if ANGLE namespace is set.
     android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
     if (ns) {
         return true;
@@ -279,10 +284,10 @@
                                                             false, systemTime() - openTime);
     } else {
         // init_angle_backend will check if loaded driver is ANGLE or not,
-        // will set cnx->useAngle appropriately.
+        // will set cnx->angleLoaded appropriately.
         // Do this here so that we use ANGLE path when driver is ANGLE (e.g. loaded as native),
         // not just loading ANGLE as option.
-        init_angle_backend(hnd->dso[2], cnx);
+        attempt_to_init_angle_backend(hnd->dso[2], cnx);
     }
 
     LOG_ALWAYS_FATAL_IF(!hnd,
@@ -324,7 +329,7 @@
     delete hnd;
     cnx->dso = nullptr;
 
-    cnx->useAngle = false;
+    cnx->angleLoaded = false;
 }
 
 void Loader::init_api(void* dso,
@@ -565,14 +570,14 @@
     return hnd;
 }
 
-void Loader::init_angle_backend(void* dso, egl_connection_t* cnx) {
+void Loader::attempt_to_init_angle_backend(void* dso, egl_connection_t* cnx) {
     void* pANGLEGetDisplayPlatform = dlsym(dso, "ANGLEGetDisplayPlatform");
     if (pANGLEGetDisplayPlatform) {
-        ALOGV("ANGLE GLES library in use");
-        cnx->useAngle = true;
+        ALOGV("ANGLE GLES library loaded");
+        cnx->angleLoaded = true;
     } else {
-        ALOGV("Native GLES library in use");
-        cnx->useAngle = false;
+        ALOGV("Native GLES library loaded");
+        cnx->angleLoaded = false;
     }
 }
 
diff --git a/opengl/libs/EGL/Loader.h b/opengl/libs/EGL/Loader.h
index 81742ab..cadbd46 100644
--- a/opengl/libs/EGL/Loader.h
+++ b/opengl/libs/EGL/Loader.h
@@ -57,7 +57,7 @@
     driver_t* attempt_to_load_system_driver(egl_connection_t* cnx, const char* suffix, const bool exact);
     void unload_system_driver(egl_connection_t* cnx);
     void initialize_api(void* dso, egl_connection_t* cnx, uint32_t mask);
-    void init_angle_backend(void* dso, egl_connection_t* cnx);
+    void attempt_to_init_angle_backend(void* dso, egl_connection_t* cnx);
 
     static __attribute__((noinline)) void init_api(void* dso, const char* const* api,
                                                    const char* const* ref_api,
diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp
index 525fed1..3317347 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -191,7 +191,7 @@
     if (cnx->dso) {
         EGLDisplay dpy = EGL_NO_DISPLAY;
 
-        if (cnx->useAngle) {
+        if (cnx->angleLoaded) {
             EGLint error;
             dpy = getPlatformDisplayAngle(display, cnx, attrib_list, &error);
             if (error != EGL_NONE) {
@@ -324,7 +324,7 @@
 
         // b/269060366 Conditionally enabled EGL_ANDROID_get_frame_timestamps extension if the
         // device's present timestamps are reliable (which may not be the case on emulators).
-        if (cnx->useAngle) {
+        if (cnx->angleLoaded) {
             if (android::base::GetBoolProperty("service.sf.present_timestamp", false)) {
                 mExtensionString.append("EGL_ANDROID_get_frame_timestamps ");
             }
@@ -432,7 +432,7 @@
         egl_connection_t* const cnx = &gEGLImpl;
         if (cnx->dso && disp.state == egl_display_t::INITIALIZED) {
             // If we're using ANGLE reset any custom DisplayPlatform
-            if (cnx->useAngle) {
+            if (cnx->angleLoaded) {
                 angle::resetAnglePlatform(disp.dpy);
             }
             if (cnx->egl.eglTerminate(disp.dpy) == EGL_FALSE) {
diff --git a/opengl/libs/EGL/egl_object.cpp b/opengl/libs/EGL/egl_object.cpp
index efbe613..33a77c4 100644
--- a/opengl/libs/EGL/egl_object.cpp
+++ b/opengl/libs/EGL/egl_object.cpp
@@ -84,7 +84,7 @@
     if (win != nullptr && connected) {
         // NOTE: When using Vulkan backend, the Vulkan runtime makes all the
         // native_window_* calls, so don't do them here.
-        if (!cnx->useAngle) {
+        if (!cnx->angleLoaded) {
             native_window_set_buffers_format(win, 0);
             if (native_window_api_disconnect(win, NATIVE_WINDOW_API_EGL)) {
                 ALOGW("EGLNativeWindowType %p disconnect failed", win);
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 48718bb..88001b2 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -680,7 +680,7 @@
 
     // NOTE: When using Vulkan backend, the Vulkan runtime makes all the
     // native_window_* calls, so don't do them here.
-    if (!cnx->useAngle) {
+    if (!cnx->angleLoaded) {
         int result = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
         if (result < 0) {
             ALOGE("eglCreateWindowSurface: native_window_api_connect (win=%p) "
@@ -699,14 +699,14 @@
     std::vector<AttrType> strippedAttribList;
     if (!processAttributes<AttrType>(dp, window, attrib_list, &colorSpace, &strippedAttribList)) {
         ALOGE("error invalid colorspace: %d", colorSpace);
-        if (!cnx->useAngle) {
+        if (!cnx->angleLoaded) {
             native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
         }
         return EGL_NO_SURFACE;
     }
     attrib_list = strippedAttribList.data();
 
-    if (!cnx->useAngle) {
+    if (!cnx->angleLoaded) {
         int err = native_window_set_buffers_format(window, static_cast<int>(format));
         if (err != 0) {
             ALOGE("error setting native window pixel format: %s (%d)", strerror(-err), err);
@@ -738,7 +738,7 @@
     }
 
     // EGLSurface creation failed
-    if (!cnx->useAngle) {
+    if (!cnx->angleLoaded) {
         native_window_set_buffers_format(window, 0);
         native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
     }
@@ -1349,7 +1349,7 @@
         }
     }
 
-    if (!s->cnx->useAngle) {
+    if (!s->cnx->angleLoaded) {
         if (!sendSurfaceMetadata(s)) {
             native_window_api_disconnect(s->getNativeWindow(), NATIVE_WINDOW_API_EGL);
             return setError(EGL_BAD_NATIVE_WINDOW, (EGLBoolean)EGL_FALSE);
@@ -1374,7 +1374,7 @@
         androidRect.bottom = y;
         androidRects.push_back(androidRect);
     }
-    if (!s->cnx->useAngle) {
+    if (!s->cnx->angleLoaded) {
         native_window_set_surface_damage(s->getNativeWindow(), androidRects.data(),
                                          androidRects.size());
     }
@@ -1465,7 +1465,7 @@
         int err = native_window_set_auto_refresh(s->getNativeWindow(), value != 0);
         if (err != 0) {
             return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
-        } else if (!s->cnx->useAngle) {
+        } else if (!s->cnx->angleLoaded) {
             return EGL_TRUE;
         } // else if ANGLE, fall through to the call to the driver (i.e. ANGLE) below
     }
@@ -1479,7 +1479,7 @@
         int err = native_window_enable_frame_timestamps(s->getNativeWindow(), value != 0);
         if (err != 0) {
             return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
-        } else if (!s->cnx->useAngle) {
+        } else if (!s->cnx->angleLoaded) {
             return EGL_TRUE;
         } // else if ANGLE, fall through to the call to the driver (i.e. ANGLE) below
     }
diff --git a/opengl/libs/EGL/egldefs.h b/opengl/libs/EGL/egldefs.h
index fcc11f1..3bd37cb 100644
--- a/opengl/libs/EGL/egldefs.h
+++ b/opengl/libs/EGL/egldefs.h
@@ -41,7 +41,8 @@
             libEgl(nullptr),
             libGles1(nullptr),
             libGles2(nullptr),
-            systemDriverUnloaded(false) {
+            systemDriverUnloaded(false),
+            angleLoaded(false) {
         const char* const* entries = platform_names;
         EGLFuncPointer* curr = reinterpret_cast<EGLFuncPointer*>(&platform);
         while (*entries) {
@@ -73,7 +74,7 @@
     void* libGles2;
 
     bool systemDriverUnloaded;
-    bool useAngle; // Was ANGLE successfully loaded
+    bool angleLoaded; // Was ANGLE successfully loaded
 };
 
 extern gl_hooks_t gHooks[2];
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index ffcc967..dc7c75a 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -106,6 +106,13 @@
                 "libstatspull",
                 "libstatssocket",
             ],
+            include_dirs: [
+                "bionic/libc/kernel/android/uapi/",
+                "bionic/libc/kernel/uapi",
+            ],
+            cflags: [
+                "-D__ANDROID_HOST__",
+            ],
         },
     },
 }
@@ -230,6 +237,9 @@
         "inputflinger",
         "libinputflingerhost",
 
+        // rust targets
+        "libinput_rust_test",
+
         // native fuzzers
         "inputflinger_latencytracker_fuzzer",
         "inputflinger_cursor_input_fuzzer",
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index 1c3293b..5df95b2 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -40,6 +40,9 @@
 const bool DEBUG = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
 
 int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus) {
+    // When adding cases to this switch, also add them to the copy of this method in
+    // TouchpadInputMapper.cpp.
+    // TODO(b/286394420): deduplicate this method with the one in TouchpadInputMapper.cpp.
     switch (linuxBus) {
         case BUS_USB:
             return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB;
diff --git a/services/inputflinger/dispatcher/DebugConfig.cpp b/services/inputflinger/dispatcher/DebugConfig.cpp
index 764194d..12122fd 100644
--- a/services/inputflinger/dispatcher/DebugConfig.cpp
+++ b/services/inputflinger/dispatcher/DebugConfig.cpp
@@ -30,11 +30,10 @@
 bool debugInboundEventDetails() {
     if (!IS_DEBUGGABLE_BUILD) {
         static const bool DEBUG_INBOUND_EVENT_DETAILS =
-                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent",
-                                          ANDROID_LOG_INFO);
+                android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "InboundEvent");
         return DEBUG_INBOUND_EVENT_DETAILS;
     }
-    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO);
+    return android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "InboundEvent");
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h
index 0e260a7..7a41d68 100644
--- a/services/inputflinger/dispatcher/DebugConfig.h
+++ b/services/inputflinger/dispatcher/DebugConfig.h
@@ -18,8 +18,7 @@
 
 #define LOG_TAG "InputDispatcher"
 
-#include <log/log.h>
-#include <log/log_event_list.h>
+#include <android-base/logging.h>
 
 namespace android::inputdispatcher {
 
@@ -42,14 +41,14 @@
  * Enable this via "adb shell setprop log.tag.InputDispatcherOutboundEvent DEBUG" (requires restart)
  */
 const bool DEBUG_OUTBOUND_EVENT_DETAILS =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundEvent", ANDROID_LOG_INFO);
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "OutboundEvent");
 
 /**
  * Log debug messages about the dispatch cycle.
  * Enable this via "adb shell setprop log.tag.InputDispatcherDispatchCycle DEBUG" (requires restart)
  */
 const bool DEBUG_DISPATCH_CYCLE =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "DispatchCycle", ANDROID_LOG_INFO);
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "DispatchCycle");
 
 /**
  * Log debug messages about channel creation
@@ -57,28 +56,28 @@
  * restart)
  */
 const bool DEBUG_CHANNEL_CREATION =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "ChannelCreation", ANDROID_LOG_INFO);
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "ChannelCreation");
 
 /**
  * Log debug messages about input event injection.
  * Enable this via "adb shell setprop log.tag.InputDispatcherInjection DEBUG" (requires restart)
  */
 const bool DEBUG_INJECTION =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Injection", ANDROID_LOG_INFO);
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Injection");
 
 /**
  * Log debug messages about input focus tracking.
  * Enable this via "adb shell setprop log.tag.InputDispatcherFocus DEBUG" (requires restart)
  */
 const bool DEBUG_FOCUS =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Focus", ANDROID_LOG_INFO);
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Focus");
 
 /**
  * Log debug messages about touch mode event
  * Enable this via "adb shell setprop log.tag.InputDispatcherTouchMode DEBUG" (requires restart)
  */
 const bool DEBUG_TOUCH_MODE =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchMode", ANDROID_LOG_INFO);
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "TouchMode");
 
 /**
  * Log debug messages about touch occlusion
@@ -90,13 +89,20 @@
  * Enable this via "adb shell setprop log.tag.InputDispatcherAppSwitch DEBUG" (requires restart)
  */
 const bool DEBUG_APP_SWITCH =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "AppSwitch", ANDROID_LOG_INFO);
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "AppSwitch");
 
 /**
  * Log debug messages about hover events.
  * Enable this via "adb shell setprop log.tag.InputDispatcherHover DEBUG" (requires restart)
  */
 const bool DEBUG_HOVER =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Hover", ANDROID_LOG_INFO);
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Hover");
+
+/**
+ * Crash if a bad stream from InputListener is detected.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherVerifyEvents DEBUG" (requires restart)
+ */
+const bool DEBUG_VERIFY_EVENTS =
+        android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "VerifyEvents");
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 010e181..34af1ae 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -26,6 +26,7 @@
 #include <android/os/IInputConstants.h>
 #include <binder/Binder.h>
 #include <ftl/enum.h>
+#include <log/log_event_list.h>
 #if defined(__ANDROID__)
 #include <gui/SurfaceComposerClient.h>
 #endif
@@ -4355,6 +4356,18 @@
         return;
     }
 
+    if (DEBUG_VERIFY_EVENTS) {
+        auto [it, _] =
+                mVerifiersByDisplay.try_emplace(args.displayId,
+                                                StringPrintf("display %" PRId32, args.displayId));
+        Result<void> result =
+                it->second.processMovement(args.deviceId, args.action, args.pointerCount,
+                                           args.pointerProperties, args.pointerCoords, args.flags);
+        if (!result.ok()) {
+            LOG(FATAL) << "Bad stream: " << result.error() << " caused by " << args.dump();
+        }
+    }
+
     uint32_t policyFlags = args.policyFlags;
     policyFlags |= POLICY_FLAG_TRUSTED;
 
@@ -6701,6 +6714,7 @@
         std::erase(mIneligibleDisplaysForPointerCapture, displayId);
         // Remove the associated touch mode state.
         mTouchModePerDisplay.erase(displayId);
+        mVerifiersByDisplay.erase(displayId);
     } // release lock
 
     // Wake up poll loop since it may need to make new input dispatching choices.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index c2add6b..58283c9 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -683,6 +683,7 @@
                                   const std::string& reason) REQUIRES(mLock);
     void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
             REQUIRES(mLock);
+    std::map<int32_t /*displayId*/, InputVerifier> mVerifiersByDisplay;
     bool afterKeyEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
                                           DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
                                           bool handled) REQUIRES(mLock);
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index b0edb57..ccb8773 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -98,12 +98,14 @@
         android: {
             shared_libs: [
                 "libinput",
+                "libstatspull",
             ],
         },
         host: {
             static_libs: [
                 "libinput",
                 "libbinder",
+                "libstatspull",
             ],
         },
     },
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 04747cc..4d0e13e 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -864,6 +864,30 @@
                         strerror(errno));
 }
 
+void EventHub::populateDeviceAbsoluteAxisInfo(Device& device) {
+    for (int axis = 0; axis <= ABS_MAX; axis++) {
+        if (!device.absBitmask.test(axis)) {
+            continue;
+        }
+        struct input_absinfo info {};
+        if (ioctl(device.fd, EVIOCGABS(axis), &info)) {
+            ALOGE("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
+                  device.identifier.name.c_str(), device.fd, errno);
+            continue;
+        }
+        if (info.minimum == info.maximum) {
+            continue;
+        }
+        RawAbsoluteAxisInfo& outAxisInfo = device.rawAbsoluteAxisInfoCache[axis];
+        outAxisInfo.valid = true;
+        outAxisInfo.minValue = info.minimum;
+        outAxisInfo.maxValue = info.maximum;
+        outAxisInfo.flat = info.flat;
+        outAxisInfo.fuzz = info.fuzz;
+        outAxisInfo.resolution = info.resolution;
+    }
+}
+
 InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
@@ -894,31 +918,20 @@
 status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
                                        RawAbsoluteAxisInfo* outAxisInfo) const {
     outAxisInfo->clear();
-
-    if (axis >= 0 && axis <= ABS_MAX) {
-        std::scoped_lock _l(mLock);
-
-        Device* device = getDeviceLocked(deviceId);
-        if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
-            struct input_absinfo info;
-            if (ioctl(device->fd, EVIOCGABS(axis), &info)) {
-                ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
-                      device->identifier.name.c_str(), device->fd, errno);
-                return -errno;
-            }
-
-            if (info.minimum != info.maximum) {
-                outAxisInfo->valid = true;
-                outAxisInfo->minValue = info.minimum;
-                outAxisInfo->maxValue = info.maximum;
-                outAxisInfo->flat = info.flat;
-                outAxisInfo->fuzz = info.fuzz;
-                outAxisInfo->resolution = info.resolution;
-            }
-            return OK;
-        }
+    if (axis < 0 || axis > ABS_MAX) {
+        return -1;
     }
-    return -1;
+    std::scoped_lock _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        return -1;
+    }
+    auto it = device->rawAbsoluteAxisInfoCache.find(axis);
+    if (it == device->rawAbsoluteAxisInfoCache.end()) {
+        return -1;
+    }
+    *outAxisInfo = it->second;
+    return OK;
 }
 
 bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -2435,6 +2448,9 @@
 
     device->configureFd();
 
+    // read absolute axis info for all available axes for the device
+    populateDeviceAbsoluteAxisInfo(*device);
+
     ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=%s, "
           "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, ",
           deviceId, fd, devicePath.c_str(), device->identifier.name.c_str(),
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 20612c7..024187f 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -615,6 +615,7 @@
         std::unique_ptr<PropertyMap> configuration;
         std::unique_ptr<VirtualKeyMap> virtualKeyMap;
         KeyMap keyMap;
+        std::unordered_map<int /*axis*/, RawAbsoluteAxisInfo> rawAbsoluteAxisInfoCache;
 
         bool ffEffectPlaying;
         int16_t ffEffectId; // initially -1
@@ -717,6 +718,13 @@
     void addDeviceInputInotify();
     void addDeviceInotify();
 
+    /**
+     * AbsoluteAxisInfo remains unchanged for the lifetime of the device, hence
+     * we can read and store it with device
+     * @param device target device
+     */
+    static void populateDeviceAbsoluteAxisInfo(Device& device);
+
     // Protect all internal state.
     mutable std::mutex mLock;
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index f4d50b8..f2b0a4b 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -1889,9 +1889,9 @@
         uint32_t id = mCurrentRawState.rawPointerData.touchingIdBits.firstMarkedBit();
         const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id);
         // Skip checking whether the pointer is inside the physical frame if the device is in
-        // unscaled mode.
+        // unscaled or pointer mode.
         if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) &&
-            mDeviceMode != DeviceMode::UNSCALED) {
+            mDeviceMode != DeviceMode::UNSCALED && mDeviceMode != DeviceMode::POINTER) {
             // If exactly one pointer went down, check for virtual key hit.
             // Otherwise, we will drop the entire stroke.
             if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index c72425a..ca4dd1e 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -16,8 +16,11 @@
 
 #include "../Macros.h"
 
+#include <algorithm>
 #include <chrono>
+#include <iterator>
 #include <limits>
+#include <map>
 #include <optional>
 
 #include <android-base/stringprintf.h>
@@ -26,6 +29,8 @@
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
+#include <stats_pull_atom_callback.h>
+#include <statslog.h>
 #include "TouchCursorInputMapperCommon.h"
 #include "TouchpadInputMapper.h"
 #include "ui/Rotation.h"
@@ -169,6 +174,106 @@
     mapper->consumeGesture(gesture);
 }
 
+int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus) {
+    // When adding cases to this switch, also add them to the copy of this method in
+    // InputDeviceMetricsCollector.cpp.
+    // TODO(b/286394420): deduplicate this method with the one in InputDeviceMetricsCollector.cpp.
+    switch (linuxBus) {
+        case BUS_USB:
+            return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB;
+        case BUS_BLUETOOTH:
+            return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__BLUETOOTH;
+        default:
+            return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__OTHER;
+    }
+}
+
+class MetricsAccumulator {
+public:
+    static MetricsAccumulator& getInstance() {
+        static MetricsAccumulator sAccumulator;
+        return sAccumulator;
+    }
+
+    void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].fingers++; }
+
+    void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].palms++; }
+
+    // Checks whether a Gesture struct is for the end of a gesture that we log metrics for, and
+    // records it if so.
+    void processGesture(const TouchpadInputMapper::MetricsIdentifier& id, const Gesture& gesture) {
+        switch (gesture.type) {
+            case kGestureTypeFling:
+                if (gesture.details.fling.fling_state == GESTURES_FLING_START) {
+                    // Indicates the end of a two-finger scroll gesture.
+                    mCounters[id].twoFingerSwipeGestures++;
+                }
+                break;
+            case kGestureTypeSwipeLift:
+                mCounters[id].threeFingerSwipeGestures++;
+                break;
+            case kGestureTypeFourFingerSwipeLift:
+                mCounters[id].fourFingerSwipeGestures++;
+                break;
+            case kGestureTypePinch:
+                if (gesture.details.pinch.zoom_state == GESTURES_ZOOM_END) {
+                    mCounters[id].pinchGestures++;
+                }
+                break;
+            default:
+                // We're not interested in any other gestures.
+                break;
+        }
+    }
+
+private:
+    MetricsAccumulator() {
+        AStatsManager_setPullAtomCallback(android::util::TOUCHPAD_USAGE, /*metadata=*/nullptr,
+                                          MetricsAccumulator::pullAtomCallback, /*cookie=*/nullptr);
+    }
+
+    ~MetricsAccumulator() { AStatsManager_clearPullAtomCallback(android::util::TOUCHPAD_USAGE); }
+
+    static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag,
+                                                                 AStatsEventList* outEventList,
+                                                                 void* cookie) {
+        LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE);
+        MetricsAccumulator& accumulator = MetricsAccumulator::getInstance();
+        accumulator.produceAtoms(outEventList);
+        accumulator.resetCounters();
+        return AStatsManager_PULL_SUCCESS;
+    }
+
+    void produceAtoms(AStatsEventList* outEventList) const {
+        for (auto& [id, counters] : mCounters) {
+            auto [busId, vendorId, productId, versionId] = id;
+            addAStatsEvent(outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
+                           versionId, linuxBusToInputDeviceBusEnum(busId), counters.fingers,
+                           counters.palms, counters.twoFingerSwipeGestures,
+                           counters.threeFingerSwipeGestures, counters.fourFingerSwipeGestures,
+                           counters.pinchGestures);
+        }
+    }
+
+    void resetCounters() { mCounters.clear(); }
+
+    // Stores the counters for a specific touchpad model. Fields have the same meanings as those of
+    // the TouchpadUsage atom; see that definition for detailed documentation.
+    struct Counters {
+        int32_t fingers = 0;
+        int32_t palms = 0;
+
+        int32_t twoFingerSwipeGestures = 0;
+        int32_t threeFingerSwipeGestures = 0;
+        int32_t fourFingerSwipeGestures = 0;
+        int32_t pinchGestures = 0;
+    };
+
+    // Metrics are aggregated by device model and version, so if two devices of the same model and
+    // version are connected at once, they will have the same counters.
+    std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters;
+};
+
 } // namespace
 
 TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext,
@@ -178,7 +283,8 @@
         mPointerController(getContext()->getPointerController(getDeviceId())),
         mStateConverter(deviceContext, mMotionAccumulator),
         mGestureConverter(*getContext(), deviceContext, getDeviceId()),
-        mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()) {
+        mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
+        mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) {
     RawAbsoluteAxisInfo slotAxisInfo;
     deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
     if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
@@ -331,12 +437,39 @@
     }
     std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
     if (state) {
+        updatePalmDetectionMetrics();
         return sendHardwareState(rawEvent->when, rawEvent->readTime, *state);
     } else {
         return {};
     }
 }
 
+void TouchpadInputMapper::updatePalmDetectionMetrics() {
+    std::set<int32_t> currentTrackingIds;
+    for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
+        const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(i);
+        if (!slot.isInUse()) {
+            continue;
+        }
+        currentTrackingIds.insert(slot.getTrackingId());
+        if (slot.getToolType() == ToolType::PALM) {
+            mPalmTrackingIds.insert(slot.getTrackingId());
+        }
+    }
+    std::vector<int32_t> liftedTouches;
+    std::set_difference(mLastFrameTrackingIds.begin(), mLastFrameTrackingIds.end(),
+                        currentTrackingIds.begin(), currentTrackingIds.end(),
+                        std::inserter(liftedTouches, liftedTouches.begin()));
+    for (int32_t trackingId : liftedTouches) {
+        if (mPalmTrackingIds.erase(trackingId) > 0) {
+            MetricsAccumulator::getInstance().recordPalm(mMetricsId);
+        } else {
+            MetricsAccumulator::getInstance().recordFinger(mMetricsId);
+        }
+    }
+    mLastFrameTrackingIds = currentTrackingIds;
+}
+
 std::list<NotifyArgs> TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime,
                                                              SelfContainedHardwareState schs) {
     ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "New hardware state: %s", schs.state.String().c_str());
@@ -363,8 +496,10 @@
 
 std::list<NotifyArgs> TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out = {};
+    MetricsAccumulator& metricsAccumulator = MetricsAccumulator::getInstance();
     for (Gesture& gesture : mGesturesToProcess) {
         out += mGestureConverter.handleGesture(when, readTime, gesture);
+        metricsAccumulator.processGesture(mMetricsId, gesture);
     }
     mGesturesToProcess.clear();
     return out;
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 23d0fd3..73ca5af 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -18,6 +18,7 @@
 
 #include <list>
 #include <memory>
+#include <set>
 #include <vector>
 
 #include <PointerControllerInterface.h>
@@ -58,10 +59,16 @@
 
     void consumeGesture(const Gesture* gesture);
 
+    // A subset of InputDeviceIdentifier used for logging metrics, to avoid storing a copy of the
+    // strings in that bigger struct.
+    using MetricsIdentifier = std::tuple<uint16_t /*busId*/, uint16_t /*vendorId*/,
+                                         uint16_t /*productId*/, uint16_t /*version*/>;
+
 private:
     void resetGestureInterpreter(nsecs_t when);
     explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
                                  const InputReaderConfiguration& readerConfig);
+    void updatePalmDetectionMetrics();
     [[nodiscard]] std::list<NotifyArgs> sendHardwareState(nsecs_t when, nsecs_t readTime,
                                                           SelfContainedHardwareState schs);
     [[nodiscard]] std::list<NotifyArgs> processGestures(nsecs_t when, nsecs_t readTime);
@@ -86,6 +93,15 @@
     bool mProcessing = false;
     bool mResettingInterpreter = false;
     std::vector<Gesture> mGesturesToProcess;
+
+    static MetricsIdentifier metricsIdFromInputDeviceIdentifier(const InputDeviceIdentifier& id) {
+        return std::make_tuple(id.bus, id.vendor, id.product, id.version);
+    }
+    const MetricsIdentifier mMetricsId;
+    // Tracking IDs for touches on the pad in the last evdev frame.
+    std::set<int32_t> mLastFrameTrackingIds;
+    // Tracking IDs for touches that have at some point been reported as palms by the touchpad.
+    std::set<int32_t> mPalmTrackingIds;
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/EventBuilders.h b/services/inputflinger/tests/EventBuilders.h
index 606a57d..09438e9 100644
--- a/services/inputflinger/tests/EventBuilders.h
+++ b/services/inputflinger/tests/EventBuilders.h
@@ -242,6 +242,10 @@
             mRawYCursorPosition = pointerCoords[0].getY();
         }
 
+        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+            addFlag(AMOTION_EVENT_FLAG_CANCELED);
+        }
+
         return {InputEvent::nextId(),
                 mEventTime,
                 /*readTime=*/mEventTime,
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e14e77e..485bf9ad 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -8733,6 +8733,8 @@
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
                                                  AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
@@ -8774,6 +8776,8 @@
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
                                                  AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
@@ -8815,6 +8819,8 @@
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
                                                  AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ADISPLAY_ID_DEFAULT));
     window->assertNoEvents();
 
     // When the window is no longer obscured because it went on top, it should get input
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 327513d..b0d6bd3 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -5903,6 +5903,40 @@
             mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
 }
 
+TEST_F(SingleTouchInputMapperTest, Process_DoesntCheckPhysicalFrameForTouchpads) {
+    std::shared_ptr<FakePointerController> fakePointerController =
+            std::make_shared<FakePointerController>();
+    mFakePolicy->setPointerController(fakePointerController);
+
+    addConfigurationProperty("touch.deviceType", "pointer");
+    prepareAxes(POSITION);
+    prepareDisplay(ui::ROTATION_0);
+    auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+    // Set a physical frame in the display viewport.
+    auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    viewport->physicalLeft = 20;
+    viewport->physicalTop = 600;
+    viewport->physicalRight = 30;
+    viewport->physicalBottom = 610;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+    // Start the touch.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1);
+    processSync(mapper);
+
+    // Expect all input starting outside the physical frame to result in NotifyMotionArgs being
+    // produced.
+    const std::array<Point, 6> outsidePoints = {
+            {{0, 0}, {19, 605}, {31, 605}, {25, 599}, {25, 611}, {DISPLAY_WIDTH, DISPLAY_HEIGHT}}};
+    for (const auto& p : outsidePoints) {
+        processMove(mapper, toRawX(p.x), toRawY(p.y));
+        processSync(mapper);
+        EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+    }
+}
+
 TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 55c2db6..47b0824 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -21,52 +21,35 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_fuzz {
-    name: "inputflinger_latencytracker_fuzzer",
-    defaults: [
-        "inputflinger_defaults",
-    ],
-    include_dirs: [
-        "frameworks/native/services/inputflinger",
-    ],
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "liblog",
-        "libutils",
-        "libinput",
-        "libinputflinger",
-    ],
-    srcs: [
-        "LatencyTrackerFuzzer.cpp",
-    ],
-    fuzz_config: {
-        cc: ["android-framework-input@google.com"],
-    },
-}
-
 cc_defaults {
     name: "inputflinger_fuzz_defaults",
     defaults: [
         "inputflinger_defaults",
+        "libinputflinger_defaults",
     ],
+    host_supported: true,
     include_dirs: [
         "frameworks/native/services/inputflinger",
     ],
     shared_libs: [
-        "android.hardware.input.classifier@1.0",
-        "android.hardware.input.processor-V1-ndk",
-        "libbase",
-        "libbinder",
-        "libcutils",
-        "liblog",
-        "libutils",
-        "libinput",
-        "libinputflinger",
         "libinputreader",
         "libinputflinger_base",
-        "libstatslog",
     ],
+    sanitize: {
+        hwaddress: true,
+        undefined: true,
+        all_undefined: true,
+        diag: {
+            undefined: true,
+        },
+    },
+    target: {
+        host: {
+            sanitize: {
+                address: true,
+            },
+        },
+    },
     header_libs: [
         "libbatteryservice_headers",
         "libinputreader_headers",
@@ -145,3 +128,17 @@
         "InputClassifierFuzzer.cpp",
     ],
 }
+
+cc_fuzz {
+    name: "inputflinger_latencytracker_fuzzer",
+    defaults: [
+        "inputflinger_fuzz_defaults",
+        "libinputdispatcher_defaults",
+    ],
+    shared_libs: [
+        "libinputreporter",
+    ],
+    srcs: [
+        "LatencyTrackerFuzzer.cpp",
+    ],
+}
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index cd9515c..c9eb9c4 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -28,6 +28,14 @@
 
 using namespace ftl::flag_operators;
 
+namespace {
+// Returns true if the layer is root of a display and can be mirrored by mirroringLayer
+bool canMirrorRootLayer(RequestedLayerState& mirroringLayer, RequestedLayerState& rootLayer) {
+    return rootLayer.isRoot() && rootLayer.layerStack == mirroringLayer.layerStackToMirror &&
+            rootLayer.id != mirroringLayer.id;
+}
+} // namespace
+
 void LayerLifecycleManager::addLayers(std::vector<std::unique_ptr<RequestedLayerState>> newLayers) {
     if (newLayers.empty()) {
         return;
@@ -46,11 +54,15 @@
         layer.parentId = linkLayer(layer.parentId, layer.id);
         layer.relativeParentId = linkLayer(layer.relativeParentId, layer.id);
         if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) {
+            // Set mirror layer's default layer stack to -1 so it doesn't end up rendered on a
+            // display accidentally.
+            layer.layerStack = ui::INVALID_LAYER_STACK;
+
             // if this layer is mirroring a display, then walk though all the existing root layers
             // for the layer stack and add them as children to be mirrored.
             mDisplayMirroringLayers.emplace_back(layer.id);
             for (auto& rootLayer : mLayers) {
-                if (rootLayer->isRoot() && rootLayer->layerStack == layer.layerStackToMirror) {
+                if (canMirrorRootLayer(layer, *rootLayer)) {
                     layer.mirrorIds.emplace_back(rootLayer->id);
                     linkLayer(rootLayer->id, layer.id);
                 }
@@ -383,10 +395,9 @@
 // and updates its list of layers that its mirroring. This function should be called when a new
 // root layer is added, removed or moved to another display.
 void LayerLifecycleManager::updateDisplayMirrorLayers(RequestedLayerState& rootLayer) {
-    for (uint32_t mirrorLayerId : mDisplayMirroringLayers) {
-        RequestedLayerState* mirrorLayer = getLayerFromId(mirrorLayerId);
-        bool canBeMirrored =
-                rootLayer.isRoot() && rootLayer.layerStack == mirrorLayer->layerStackToMirror;
+    for (uint32_t mirroringLayerId : mDisplayMirroringLayers) {
+        RequestedLayerState* mirrorLayer = getLayerFromId(mirroringLayerId);
+        bool canBeMirrored = canMirrorRootLayer(*mirrorLayer, rootLayer);
         bool currentlyMirrored =
                 std::find(mirrorLayer->mirrorIds.begin(), mirrorLayer->mirrorIds.end(),
                           rootLayer.id) != mirrorLayer->mirrorIds.end();
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index ae3ffc6..806b502 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -31,6 +31,7 @@
 #include "LayerLog.h"
 #include "LayerSnapshotBuilder.h"
 #include "TimeStats/TimeStats.h"
+#include "Tracing/TransactionTracing.h"
 
 namespace android::surfaceflinger::frontend {
 
@@ -435,13 +436,13 @@
         // multiple children.
         LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root, args.root.getLayer()->id,
                                                                 LayerHierarchy::Variant::Attached);
-        updateSnapshotsInHierarchy(args, args.root, root, mRootSnapshot);
+        updateSnapshotsInHierarchy(args, args.root, root, mRootSnapshot, /*depth=*/0);
     } else {
         for (auto& [childHierarchy, variant] : args.root.mChildren) {
             LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root,
                                                                     childHierarchy->getLayer()->id,
                                                                     variant);
-            updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot);
+            updateSnapshotsInHierarchy(args, *childHierarchy, root, mRootSnapshot, /*depth=*/0);
         }
     }
 
@@ -494,7 +495,15 @@
 
 const LayerSnapshot& LayerSnapshotBuilder::updateSnapshotsInHierarchy(
         const Args& args, const LayerHierarchy& hierarchy,
-        LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot) {
+        LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot,
+        int depth) {
+    if (depth > 50) {
+        TransactionTraceWriter::getInstance().invoke("layer_builder_stack_overflow_",
+                                                     /*overwrite=*/false);
+        LOG_ALWAYS_FATAL("Cycle detected in LayerSnapshotBuilder. See "
+                         "builder_stack_overflow_transactions.winscope");
+    }
+
     const RequestedLayerState* layer = hierarchy.getLayer();
     LayerSnapshot* snapshot = getSnapshot(traversalPath);
     const bool newSnapshot = snapshot == nullptr;
@@ -517,7 +526,8 @@
                                                                 childHierarchy->getLayer()->id,
                                                                 variant);
         const LayerSnapshot& childSnapshot =
-                updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot);
+                updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot,
+                                           depth + 1);
         updateChildState(*snapshot, childSnapshot, args);
     }
 
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index 3f33ab8..2e46dc6 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -96,7 +96,7 @@
 
     const LayerSnapshot& updateSnapshotsInHierarchy(const Args&, const LayerHierarchy& hierarchy,
                                                     LayerHierarchy::TraversalPath& traversalPath,
-                                                    const LayerSnapshot& parentSnapshot);
+                                                    const LayerSnapshot& parentSnapshot, int depth);
     void updateSnapshot(LayerSnapshot&, const Args&, const RequestedLayerState&,
                         const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath&);
     static void updateRelativeState(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot,
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 23bb54c..bde2d05 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -131,6 +131,7 @@
     const uint32_t oldFlags = flags;
     const half oldAlpha = color.a;
     const bool hadBuffer = externalTexture != nullptr;
+    uint64_t oldFramenumber = hadBuffer ? bufferData->frameNumber : 0;
     const bool hadSideStream = sidebandStream != nullptr;
     const layer_state_t& clientState = resolvedComposerState.state;
     const bool hadBlur = hasBlur();
@@ -147,12 +148,9 @@
             changes |= RequestedLayerState::Changes::Geometry;
         }
     }
+
     if (clientState.what & layer_state_t::eBufferChanged) {
         externalTexture = resolvedComposerState.externalTexture;
-        barrierProducerId = std::max(bufferData->producerId, barrierProducerId);
-        barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber);
-        // TODO(b/277265947) log and flush transaction trace when we detect out of order updates
-
         const bool hasBuffer = externalTexture != nullptr;
         if (hasBuffer || hasBuffer != hadBuffer) {
             changes |= RequestedLayerState::Changes::Buffer;
@@ -163,6 +161,28 @@
                     RequestedLayerState::Changes::VisibleRegion |
                     RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input;
         }
+
+        if (hasBuffer) {
+            const bool frameNumberChanged =
+                    bufferData->flags.test(BufferData::BufferDataChange::frameNumberChanged);
+            const uint64_t frameNumber =
+                    frameNumberChanged ? bufferData->frameNumber : oldFramenumber + 1;
+            bufferData->frameNumber = frameNumber;
+
+            if ((barrierProducerId > bufferData->producerId) ||
+                ((barrierProducerId == bufferData->producerId) &&
+                 (barrierFrameNumber > bufferData->frameNumber))) {
+                ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
+                      " -> producedId=%d frameNumber=%" PRIu64,
+                      getDebugString().c_str(), bufferData->producerId, bufferData->frameNumber,
+                      bufferData->producerId, frameNumber);
+                TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_",
+                                                             /*overwrite=*/false);
+            }
+
+            barrierProducerId = std::max(bufferData->producerId, barrierProducerId);
+            barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber);
+        }
     }
 
     if (clientState.what & layer_state_t::eSidebandStreamChanged) {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index c413741..a5d7ce7 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -3125,6 +3125,16 @@
         return true;
     }
 
+    if ((mDrawingState.producerId > bufferData.producerId) ||
+        ((mDrawingState.producerId == bufferData.producerId) &&
+         (mDrawingState.frameNumber > frameNumber))) {
+        ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
+              " -> producedId=%d frameNumber=%" PRIu64,
+              getDebugName(), mDrawingState.producerId, mDrawingState.frameNumber,
+              bufferData.producerId, frameNumber);
+        TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_", /*overwrite=*/false);
+    }
+
     mDrawingState.producerId = bufferData.producerId;
     mDrawingState.barrierProducerId =
             std::max(mDrawingState.producerId, mDrawingState.barrierProducerId);
@@ -3132,7 +3142,6 @@
     mDrawingState.barrierFrameNumber =
             std::max(mDrawingState.frameNumber, mDrawingState.barrierFrameNumber);
 
-    // TODO(b/277265947) log and flush transaction trace when we detect out of order updates
     mDrawingState.releaseBufferListener = bufferData.releaseBufferListener;
     mDrawingState.buffer = std::move(buffer);
     mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged)
diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp
index d5d8688..6d2586a 100644
--- a/services/surfaceflinger/Scheduler/Android.bp
+++ b/services/surfaceflinger/Scheduler/Android.bp
@@ -40,6 +40,7 @@
     name: "libscheduler",
     defaults: ["libscheduler_defaults"],
     srcs: [
+        "src/FrameTargeter.cpp",
         "src/PresentLatencyTracker.cpp",
         "src/Timer.cpp",
     ],
@@ -52,6 +53,7 @@
     test_suites: ["device-tests"],
     defaults: ["libscheduler_defaults"],
     srcs: [
+        "tests/FrameTargeterTest.cpp",
         "tests/PresentLatencyTrackerTest.cpp",
         "tests/TimerTest.cpp",
     ],
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 918d401..41639b6 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -171,14 +171,21 @@
 
 void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId,
                               TimePoint expectedVsyncTime) {
-    const TimePoint frameTime = SchedulerClock::now();
+    mPacesetterFrameTargeter.beginFrame({.frameBeginTime = SchedulerClock::now(),
+                                         .vsyncId = vsyncId,
+                                         .expectedVsyncTime = expectedVsyncTime,
+                                         .sfWorkDuration =
+                                                 mVsyncModulator->getVsyncConfig().sfWorkDuration},
+                                        *getVsyncSchedule());
 
-    if (!compositor.commit(frameTime, vsyncId, expectedVsyncTime)) {
+    if (!compositor.commit(mPacesetterFrameTargeter.target())) {
         return;
     }
 
-    compositor.composite(frameTime, vsyncId);
+    const auto compositeResult = compositor.composite(mPacesetterFrameTargeter);
     compositor.sample();
+
+    mPacesetterFrameTargeter.endFrame(compositeResult);
 }
 
 std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
@@ -188,23 +195,23 @@
             .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent);
 }
 
-bool Scheduler::isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const {
+bool Scheduler::isVsyncValid(TimePoint expectedVsyncTime, uid_t uid) const {
     const auto frameRate = getFrameRateOverride(uid);
     if (!frameRate.has_value()) {
         return true;
     }
 
     ATRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str());
-    return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate);
+    return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), *frameRate);
 }
 
-bool Scheduler::isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const {
-    return getVsyncSchedule()->getTracker().isVSyncInPhase(timePoint.ns(), frameRate);
+bool Scheduler::isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const {
+    return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), frameRate);
 }
 
 impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
-    return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
-        return !isVsyncValid(TimePoint::fromNs(expectedVsyncTimestamp), uid);
+    return [this](nsecs_t expectedVsyncTime, uid_t uid) {
+        return !isVsyncValid(TimePoint::fromNs(expectedVsyncTime), uid);
     };
 }
 
@@ -716,6 +723,8 @@
 
     mFrameRateOverrideMappings.dump(dumper);
     dumper.eol();
+
+    mPacesetterFrameTargeter.dump(dumper);
 }
 
 void Scheduler::dumpVsync(std::string& out) const {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index a1354fa..17e9cea 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -35,6 +35,7 @@
 #include <ftl/fake_guard.h>
 #include <ftl/optional.h>
 #include <scheduler/Features.h>
+#include <scheduler/FrameTargeter.h>
 #include <scheduler/Time.h>
 #include <scheduler/VsyncConfig.h>
 #include <ui/DisplayId.h>
@@ -249,9 +250,11 @@
         return std::const_pointer_cast<VsyncSchedule>(std::as_const(*this).getVsyncSchedule(idOpt));
     }
 
+    const FrameTarget& pacesetterFrameTarget() { return mPacesetterFrameTargeter.target(); }
+
     // Returns true if a given vsync timestamp is considered valid vsync
     // for a given uid
-    bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const;
+    bool isVsyncValid(TimePoint expectedVsyncTime, uid_t uid) const;
 
     bool isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const;
 
@@ -446,6 +449,8 @@
     ftl::Optional<PhysicalDisplayId> mPacesetterDisplayId GUARDED_BY(mDisplayLock)
             GUARDED_BY(kMainThreadContext);
 
+    FrameTargeter mPacesetterFrameTargeter{mFeatures.test(Feature::kBackpressureGpuComposition)};
+
     ftl::Optional<DisplayRef> pacesetterDisplayLocked() REQUIRES(mDisplayLock) {
         return static_cast<const Scheduler*>(this)->pacesetterDisplayLocked().transform(
                 [](const Display& display) { return std::ref(const_cast<Display&>(display)); });
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 556ef80..c037ac8 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -20,14 +20,17 @@
 #include <memory>
 #include <string>
 
-#include <ThreadContext.h>
 #include <android-base/thread_annotations.h>
 #include <ftl/enum.h>
 #include <ftl/optional.h>
-#include <scheduler/Features.h>
-#include <scheduler/Time.h>
 #include <ui/DisplayId.h>
 
+#include <scheduler/Features.h>
+#include <scheduler/IVsyncSource.h>
+#include <scheduler/Time.h>
+
+#include "ThreadContext.h"
+
 namespace android {
 class EventThreadTest;
 class VsyncScheduleTest;
@@ -48,15 +51,16 @@
 using VsyncTracker = VSyncTracker;
 
 // Schedule that synchronizes to hardware VSYNC of a physical display.
-class VsyncSchedule {
+class VsyncSchedule final : public IVsyncSource {
 public:
     using RequestHardwareVsync = std::function<void(PhysicalDisplayId, bool enabled)>;
 
     VsyncSchedule(PhysicalDisplayId, FeatureFlags, RequestHardwareVsync);
     ~VsyncSchedule();
 
-    Period period() const;
-    TimePoint vsyncDeadlineAfter(TimePoint) const;
+    // IVsyncSource overrides:
+    Period period() const override;
+    TimePoint vsyncDeadlineAfter(TimePoint) const override;
 
     // Inform the schedule that the period is changing and the schedule needs to recalibrate
     // itself. The schedule will end the period transition internally. This will
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Features.h b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
index b3a6a60..200407d 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Features.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
@@ -23,10 +23,11 @@
 namespace android::scheduler {
 
 enum class Feature : std::uint8_t {
-    kPresentFences = 0b1,
-    kKernelIdleTimer = 0b10,
-    kContentDetection = 0b100,
-    kTracePredictedVsync = 0b1000,
+    kPresentFences = 1 << 0,
+    kKernelIdleTimer = 1 << 1,
+    kContentDetection = 1 << 2,
+    kTracePredictedVsync = 1 << 3,
+    kBackpressureGpuComposition = 1 << 4,
 };
 
 using FeatureFlags = ftl::Flags<Feature>;
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
new file mode 100644
index 0000000..85f2e64
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <memory>
+
+#include <ui/Fence.h>
+#include <ui/FenceTime.h>
+
+#include <scheduler/Time.h>
+#include <scheduler/VsyncId.h>
+#include <scheduler/interface/CompositeResult.h>
+
+// TODO(b/185536303): Pull to FTL.
+#include "../../../TracedOrdinal.h"
+#include "../../../Utils/Dumper.h"
+
+namespace android::scheduler {
+
+struct IVsyncSource;
+
+// Read-only interface to the metrics computed by FrameTargeter for the latest frame.
+class FrameTarget {
+public:
+    VsyncId vsyncId() const { return mVsyncId; }
+
+    // The time when the frame actually began, as opposed to when it had been scheduled to begin.
+    TimePoint frameBeginTime() const { return mFrameBeginTime; }
+
+    // Relative to when the frame actually began, as opposed to when it had been scheduled to begin.
+    Duration expectedFrameDuration() const { return mExpectedPresentTime - mFrameBeginTime; }
+
+    TimePoint expectedPresentTime() const { return mExpectedPresentTime; }
+
+    // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details.
+    TimePoint pastVsyncTime(Period vsyncPeriod) const;
+
+    // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
+    TimePoint previousFrameVsyncTime(Period vsyncPeriod) const {
+        return mExpectedPresentTime - vsyncPeriod;
+    }
+
+    // The present fence for the frame that had targeted the most recent VSYNC before this frame.
+    // If the target VSYNC for any given frame is more than `vsyncPeriod` in the future, then the
+    // VSYNC of at least one previous frame has not yet passed. In other words, this is NOT the
+    // `presentFenceForPreviousFrame` if running N VSYNCs ahead, but the one that should have been
+    // signaled by now (unless that frame missed).
+    const FenceTimePtr& presentFenceForPastVsync(Period vsyncPeriod) const;
+
+    // Equivalent to `presentFenceForPastVsync` unless running N VSYNCs ahead.
+    const FenceTimePtr& presentFenceForPreviousFrame() const {
+        return mPresentFences.front().fenceTime;
+    }
+
+    bool wouldPresentEarly(Period vsyncPeriod) const;
+
+    bool isFramePending() const { return mFramePending; }
+    bool didMissFrame() const { return mFrameMissed; }
+    bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; }
+
+protected:
+    ~FrameTarget() = default;
+
+    VsyncId mVsyncId;
+    TimePoint mFrameBeginTime;
+    TimePoint mExpectedPresentTime;
+
+    TracedOrdinal<bool> mFramePending{"PrevFramePending", false};
+    TracedOrdinal<bool> mFrameMissed{"PrevFrameMissed", false};
+    TracedOrdinal<bool> mHwcFrameMissed{"PrevHwcFrameMissed", false};
+    TracedOrdinal<bool> mGpuFrameMissed{"PrevGpuFrameMissed", false};
+
+    struct FenceWithFenceTime {
+        sp<Fence> fence = Fence::NO_FENCE;
+        FenceTimePtr fenceTime = FenceTime::NO_FENCE;
+    };
+    std::array<FenceWithFenceTime, 2> mPresentFences;
+
+private:
+    template <int N>
+    inline bool targetsVsyncsAhead(Period vsyncPeriod) const {
+        static_assert(N > 1);
+        return expectedFrameDuration() > (N - 1) * vsyncPeriod;
+    }
+};
+
+// Computes a display's per-frame metrics about past/upcoming targeting of present deadlines.
+class FrameTargeter final : private FrameTarget {
+public:
+    explicit FrameTargeter(bool backpressureGpuComposition)
+          : mBackpressureGpuComposition(backpressureGpuComposition) {}
+
+    const FrameTarget& target() const { return *this; }
+
+    struct BeginFrameArgs {
+        TimePoint frameBeginTime;
+        VsyncId vsyncId;
+        TimePoint expectedVsyncTime;
+        Duration sfWorkDuration;
+    };
+
+    void beginFrame(const BeginFrameArgs&, const IVsyncSource&);
+
+    // TODO(b/241285191): Merge with FrameTargeter::endFrame.
+    FenceTimePtr setPresentFence(sp<Fence>);
+
+    void endFrame(const CompositeResult&);
+
+    void dump(utils::Dumper&) const;
+
+private:
+    friend class FrameTargeterTest;
+
+    // For tests.
+    using IsFencePendingFuncPtr = bool (*)(const FenceTimePtr&, int graceTimeMs);
+    void beginFrame(const BeginFrameArgs&, const IVsyncSource&, IsFencePendingFuncPtr);
+    FenceTimePtr setPresentFence(sp<Fence>, FenceTimePtr);
+
+    static bool isFencePending(const FenceTimePtr&, int graceTimeMs);
+
+    const bool mBackpressureGpuComposition;
+
+    TimePoint mScheduledPresentTime;
+    CompositionCoverageFlags mCompositionCoverage;
+
+    std::atomic_uint mFrameMissedCount = 0;
+    std::atomic_uint mHwcFrameMissedCount = 0;
+    std::atomic_uint mGpuFrameMissedCount = 0;
+};
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
new file mode 100644
index 0000000..bb2de75
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <scheduler/Time.h>
+
+namespace android::scheduler {
+
+struct IVsyncSource {
+    virtual Period period() const = 0;
+    virtual TimePoint vsyncDeadlineAfter(TimePoint) const = 0;
+
+protected:
+    ~IVsyncSource() = default;
+};
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h
new file mode 100644
index 0000000..f795f1f
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <scheduler/interface/CompositionCoverage.h>
+
+namespace android {
+
+struct CompositeResult {
+    CompositionCoverageFlags compositionCoverage;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
index cc41925..2696076 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
@@ -18,8 +18,15 @@
 
 #include <scheduler/Time.h>
 #include <scheduler/VsyncId.h>
+#include <scheduler/interface/CompositeResult.h>
 
 namespace android {
+namespace scheduler {
+
+class FrameTarget;
+class FrameTargeter;
+
+} // namespace scheduler
 
 struct ICompositor {
     // Configures physical displays, processing hotplug and/or mode setting via the Composer HAL.
@@ -27,11 +34,11 @@
 
     // Commits transactions for layers and displays. Returns whether any state has been invalidated,
     // i.e. whether a frame should be composited for each display.
-    virtual bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) = 0;
+    virtual bool commit(const scheduler::FrameTarget&) = 0;
 
     // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition
     // via RenderEngine and the Composer HAL, respectively.
-    virtual void composite(TimePoint frameTime, VsyncId) = 0;
+    virtual CompositeResult composite(scheduler::FrameTargeter&) = 0;
 
     // Samples the composited frame via RegionSamplingThread.
     virtual void sample() = 0;
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
new file mode 100644
index 0000000..7138afd
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gui/TraceUtils.h>
+
+#include <scheduler/FrameTargeter.h>
+#include <scheduler/IVsyncSource.h>
+
+namespace android::scheduler {
+
+TimePoint FrameTarget::pastVsyncTime(Period vsyncPeriod) const {
+    // TODO(b/267315508): Generalize to N VSYNCs.
+    const int shift = static_cast<int>(targetsVsyncsAhead<2>(vsyncPeriod));
+    return mExpectedPresentTime - Period::fromNs(vsyncPeriod.ns() << shift);
+}
+
+const FenceTimePtr& FrameTarget::presentFenceForPastVsync(Period vsyncPeriod) const {
+    // TODO(b/267315508): Generalize to N VSYNCs.
+    const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(vsyncPeriod));
+    return mPresentFences[i].fenceTime;
+}
+
+bool FrameTarget::wouldPresentEarly(Period vsyncPeriod) const {
+    // TODO(b/241285475): Since this is called during `composite`, the calls to `targetsVsyncsAhead`
+    // should use `TimePoint::now()` in case of delays since `mFrameBeginTime`.
+
+    // TODO(b/267315508): Generalize to N VSYNCs.
+    if (targetsVsyncsAhead<3>(vsyncPeriod)) {
+        return true;
+    }
+
+    const auto fence = presentFenceForPastVsync(vsyncPeriod);
+    return fence->isValid() && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
+}
+
+void FrameTargeter::beginFrame(const BeginFrameArgs& args, const IVsyncSource& vsyncSource) {
+    return beginFrame(args, vsyncSource, &FrameTargeter::isFencePending);
+}
+
+void FrameTargeter::beginFrame(const BeginFrameArgs& args, const IVsyncSource& vsyncSource,
+                               IsFencePendingFuncPtr isFencePendingFuncPtr) {
+    mVsyncId = args.vsyncId;
+    mFrameBeginTime = args.frameBeginTime;
+
+    // The `expectedVsyncTime`, which was predicted when this frame was scheduled, is normally in
+    // the future relative to `frameBeginTime`, but may not be for delayed frames. Adjust
+    // `mExpectedPresentTime` accordingly, but not `mScheduledPresentTime`.
+    const TimePoint lastScheduledPresentTime = mScheduledPresentTime;
+    mScheduledPresentTime = args.expectedVsyncTime;
+
+    const Period vsyncPeriod = vsyncSource.period();
+
+    // Calculate the expected present time once and use the cached value throughout this frame to
+    // make sure all layers are seeing this same value.
+    if (args.expectedVsyncTime >= args.frameBeginTime) {
+        mExpectedPresentTime = args.expectedVsyncTime;
+    } else {
+        mExpectedPresentTime = vsyncSource.vsyncDeadlineAfter(args.frameBeginTime);
+        if (args.sfWorkDuration > vsyncPeriod) {
+            // Inflate the expected present time if we're targeting the next VSYNC.
+            mExpectedPresentTime += vsyncPeriod;
+        }
+    }
+
+    ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId),
+                  ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
+                  mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)");
+
+    const FenceTimePtr& pastPresentFence = presentFenceForPastVsync(vsyncPeriod);
+
+    // In cases where the present fence is about to fire, give it a small grace period instead of
+    // giving up on the frame.
+    //
+    // TODO(b/280667110): The grace period should depend on `sfWorkDuration` and `vsyncPeriod` being
+    // approximately equal, not whether backpressure propagation is enabled.
+    const int graceTimeForPresentFenceMs = static_cast<int>(
+            mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu));
+
+    // Pending frames may trigger backpressure propagation.
+    const auto& isFencePending = *isFencePendingFuncPtr;
+    mFramePending = pastPresentFence != FenceTime::NO_FENCE &&
+            isFencePending(pastPresentFence, graceTimeForPresentFenceMs);
+
+    // A frame is missed if the prior frame is still pending. If no longer pending, then we still
+    // count the frame as missed if the predicted present time was further in the past than when the
+    // fence actually fired. Add some slop to correct for drift. This should generally be smaller
+    // than a typical frame duration, but should not be so small that it reports reasonable drift as
+    // a missed frame.
+    mFrameMissed = mFramePending || [&] {
+        const nsecs_t pastPresentTime = pastPresentFence->getSignalTime();
+        if (pastPresentTime < 0) return false;
+        const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2;
+        return lastScheduledPresentTime.ns() < pastPresentTime - frameMissedSlop;
+    }();
+
+    mHwcFrameMissed = mFrameMissed && mCompositionCoverage.test(CompositionCoverage::Hwc);
+    mGpuFrameMissed = mFrameMissed && mCompositionCoverage.test(CompositionCoverage::Gpu);
+
+    if (mFrameMissed) mFrameMissedCount++;
+    if (mHwcFrameMissed) mHwcFrameMissedCount++;
+    if (mGpuFrameMissed) mGpuFrameMissedCount++;
+}
+
+void FrameTargeter::endFrame(const CompositeResult& result) {
+    mCompositionCoverage = result.compositionCoverage;
+}
+
+FenceTimePtr FrameTargeter::setPresentFence(sp<Fence> presentFence) {
+    auto presentFenceTime = std::make_shared<FenceTime>(presentFence);
+    return setPresentFence(std::move(presentFence), std::move(presentFenceTime));
+}
+
+FenceTimePtr FrameTargeter::setPresentFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime) {
+    mPresentFences[1] = mPresentFences[0];
+    mPresentFences[0] = {std::move(presentFence), presentFenceTime};
+    return presentFenceTime;
+}
+
+void FrameTargeter::dump(utils::Dumper& dumper) const {
+    using namespace std::string_view_literals;
+
+    utils::Dumper::Section section(dumper, "Frame Targeting"sv);
+
+    // There are scripts and tests that expect this (rather than "name=value") format.
+    dumper.dump({}, "Total missed frame count: " + std::to_string(mFrameMissedCount));
+    dumper.dump({}, "HWC missed frame count: " + std::to_string(mHwcFrameMissedCount));
+    dumper.dump({}, "GPU missed frame count: " + std::to_string(mGpuFrameMissedCount));
+}
+
+bool FrameTargeter::isFencePending(const FenceTimePtr& fence, int graceTimeMs) {
+    ATRACE_CALL();
+    const status_t status = fence->wait(graceTimeMs);
+
+    // This is the same as Fence::Status::Unsignaled, but it saves a call to getStatus,
+    // which calls wait(0) again internally.
+    return status == -ETIME;
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
new file mode 100644
index 0000000..908f214
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/optional.h>
+#include <gtest/gtest.h>
+
+#include <scheduler/Fps.h>
+#include <scheduler/FrameTargeter.h>
+#include <scheduler/IVsyncSource.h>
+
+using namespace std::chrono_literals;
+
+namespace android::scheduler {
+namespace {
+
+struct VsyncSource final : IVsyncSource {
+    VsyncSource(Period period, TimePoint deadline) : vsyncPeriod(period), vsyncDeadline(deadline) {}
+
+    const Period vsyncPeriod;
+    const TimePoint vsyncDeadline;
+
+    Period period() const override { return vsyncPeriod; }
+    TimePoint vsyncDeadlineAfter(TimePoint) const override { return vsyncDeadline; }
+};
+
+} // namespace
+
+class FrameTargeterTest : public testing::Test {
+public:
+    const auto& target() const { return mTargeter.target(); }
+
+    struct Frame {
+        Frame(FrameTargeterTest* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime,
+              Duration frameDuration, Fps refreshRate,
+              FrameTargeter::IsFencePendingFuncPtr isFencePendingFuncPtr = Frame::fenceSignaled,
+              const ftl::Optional<VsyncSource>& vsyncSourceOpt = std::nullopt)
+              : testPtr(testPtr), frameBeginTime(frameBeginTime), period(refreshRate.getPeriod()) {
+            const FrameTargeter::BeginFrameArgs args{.frameBeginTime = frameBeginTime,
+                                                     .vsyncId = vsyncId,
+                                                     .expectedVsyncTime =
+                                                             frameBeginTime + frameDuration,
+                                                     .sfWorkDuration = 10ms};
+
+            testPtr->mTargeter.beginFrame(args,
+                                          vsyncSourceOpt
+                                                  .or_else([&] {
+                                                      return std::make_optional(
+                                                              VsyncSource(period,
+                                                                          args.expectedVsyncTime));
+                                                  })
+                                                  .value(),
+                                          isFencePendingFuncPtr);
+        }
+
+        FenceTimePtr end(CompositionCoverage coverage = CompositionCoverage::Hwc) {
+            if (ended) return nullptr;
+            ended = true;
+
+            auto [fence, fenceTime] = testPtr->mFenceMap.makePendingFenceForTest();
+            testPtr->mTargeter.setPresentFence(std::move(fence), fenceTime);
+
+            testPtr->mTargeter.endFrame({.compositionCoverage = coverage});
+            return fenceTime;
+        }
+
+        ~Frame() {
+            end();
+            frameBeginTime += period;
+        }
+
+        static bool fencePending(const FenceTimePtr&, int) { return true; }
+        static bool fenceSignaled(const FenceTimePtr&, int) { return false; }
+
+        FrameTargeterTest* const testPtr;
+
+        TimePoint& frameBeginTime;
+        const Period period;
+
+        bool ended = false;
+    };
+
+private:
+    FenceToFenceTimeMap mFenceMap;
+
+    static constexpr bool kBackpressureGpuComposition = true;
+    FrameTargeter mTargeter{kBackpressureGpuComposition};
+};
+
+TEST_F(FrameTargeterTest, targetsFrames) {
+    VsyncId vsyncId{42};
+    {
+        TimePoint frameBeginTime(989ms);
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, 60_Hz);
+
+        EXPECT_EQ(target().vsyncId(), VsyncId{42});
+        EXPECT_EQ(target().frameBeginTime(), TimePoint(989ms));
+        EXPECT_EQ(target().expectedPresentTime(), TimePoint(999ms));
+        EXPECT_EQ(target().expectedFrameDuration(), 10ms);
+    }
+    {
+        TimePoint frameBeginTime(1100ms);
+        const Frame frame(this, vsyncId++, frameBeginTime, 11ms, 60_Hz);
+
+        EXPECT_EQ(target().vsyncId(), VsyncId{43});
+        EXPECT_EQ(target().frameBeginTime(), TimePoint(1100ms));
+        EXPECT_EQ(target().expectedPresentTime(), TimePoint(1111ms));
+        EXPECT_EQ(target().expectedFrameDuration(), 11ms);
+    }
+}
+
+TEST_F(FrameTargeterTest, inflatesExpectedPresentTime) {
+    // Negative such that `expectedVsyncTime` is in the past.
+    constexpr Duration kFrameDuration = -3ms;
+    TimePoint frameBeginTime(777ms);
+
+    constexpr Fps kRefreshRate = 120_Hz;
+    const VsyncSource vsyncSource(kRefreshRate.getPeriod(), frameBeginTime + 5ms);
+    const Frame frame(this, VsyncId{123}, frameBeginTime, kFrameDuration, kRefreshRate,
+                      Frame::fenceSignaled, vsyncSource);
+
+    EXPECT_EQ(target().expectedPresentTime(), vsyncSource.vsyncDeadline + vsyncSource.vsyncPeriod);
+}
+
+TEST_F(FrameTargeterTest, recallsPastVsync) {
+    VsyncId vsyncId{111};
+    TimePoint frameBeginTime(1000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 13ms;
+
+    for (int n = 5; n-- > 0;) {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate);
+        const auto fence = frame.end();
+
+        EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - kPeriod);
+        EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), fence);
+    }
+}
+
+TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAhead) {
+    VsyncId vsyncId{222};
+    TimePoint frameBeginTime(2000ms);
+    constexpr Fps kRefreshRate = 120_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 10ms;
+
+    FenceTimePtr previousFence = FenceTime::NO_FENCE;
+
+    for (int n = 5; n-- > 0;) {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate);
+        const auto fence = frame.end();
+
+        EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
+        EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), previousFence);
+
+        previousFence = fence;
+    }
+}
+
+TEST_F(FrameTargeterTest, doesNotDetectEarlyPresentIfNoFence) {
+    constexpr Period kPeriod = (60_Hz).getPeriod();
+    EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), FenceTime::NO_FENCE);
+    EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+}
+
+TEST_F(FrameTargeterTest, detectsEarlyPresent) {
+    VsyncId vsyncId{333};
+    TimePoint frameBeginTime(3000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+    // The target is not early while past present fences are pending.
+    for (int n = 3; n-- > 0;) {
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+    }
+
+    // The target is early if the past present fence was signaled.
+    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+    const auto fence = frame.end();
+    fence->signalForTest(frameBeginTime.ns());
+
+    EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+}
+
+TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) {
+    VsyncId vsyncId{444};
+    TimePoint frameBeginTime(4000ms);
+    constexpr Fps kRefreshRate = 120_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+    // The target is not early while past present fences are pending.
+    for (int n = 3; n-- > 0;) {
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+    }
+
+    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+    const auto fence = frame.end();
+    fence->signalForTest(frameBeginTime.ns());
+
+    // The target is two VSYNCs ahead, so the past present fence is still pending.
+    EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+
+    { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate); }
+
+    // The target is early if the past present fence was signaled.
+    EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+}
+
+TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) {
+    TimePoint frameBeginTime(5000ms);
+    constexpr Fps kRefreshRate = 144_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+    const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate);
+
+    // The target is more than two VSYNCs ahead, but present fences are not tracked that far back.
+    EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+}
+
+TEST_F(FrameTargeterTest, detectsMissedFrames) {
+    VsyncId vsyncId{555};
+    TimePoint frameBeginTime(5000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+    EXPECT_FALSE(target().isFramePending());
+    EXPECT_FALSE(target().didMissFrame());
+    EXPECT_FALSE(target().didMissHwcFrame());
+
+    {
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        EXPECT_FALSE(target().isFramePending());
+
+        // The frame did not miss if the past present fence is invalid.
+        EXPECT_FALSE(target().didMissFrame());
+        EXPECT_FALSE(target().didMissHwcFrame());
+    }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, Frame::fencePending);
+        EXPECT_TRUE(target().isFramePending());
+
+        // The frame missed if the past present fence is pending.
+        EXPECT_TRUE(target().didMissFrame());
+        EXPECT_TRUE(target().didMissHwcFrame());
+
+        frame.end(CompositionCoverage::Gpu);
+    }
+    {
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, Frame::fencePending);
+        EXPECT_TRUE(target().isFramePending());
+
+        // The GPU frame missed if the past present fence is pending.
+        EXPECT_TRUE(target().didMissFrame());
+        EXPECT_FALSE(target().didMissHwcFrame());
+    }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        EXPECT_FALSE(target().isFramePending());
+
+        const auto fence = frame.end();
+        const auto expectedPresentTime = target().expectedPresentTime();
+        fence->signalForTest(expectedPresentTime.ns() + kPeriod.ns() / 2 + 1);
+    }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        EXPECT_FALSE(target().isFramePending());
+
+        const auto fence = frame.end();
+        const auto expectedPresentTime = target().expectedPresentTime();
+        fence->signalForTest(expectedPresentTime.ns() + kPeriod.ns() / 2);
+
+        // The frame missed if the past present fence was signaled but not within slop.
+        EXPECT_TRUE(target().didMissFrame());
+        EXPECT_TRUE(target().didMissHwcFrame());
+    }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate);
+        EXPECT_FALSE(target().isFramePending());
+
+        // The frame did not miss if the past present fence was signaled within slop.
+        EXPECT_FALSE(target().didMissFrame());
+        EXPECT_FALSE(target().didMissHwcFrame());
+    }
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp b/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp
index 8952ca9..df2ea83 100644
--- a/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp
@@ -23,16 +23,6 @@
 #include <ui/FenceTime.h>
 
 namespace android::scheduler {
-namespace {
-
-using FencePair = std::pair<sp<Fence>, std::shared_ptr<FenceTime>>;
-
-FencePair makePendingFence(FenceToFenceTimeMap& fenceMap) {
-    const auto fence = sp<Fence>::make();
-    return {fence, fenceMap.createFenceTimeForTest(fence)};
-}
-
-} // namespace
 
 TEST(PresentLatencyTrackerTest, skipsInvalidFences) {
     PresentLatencyTracker tracker;
@@ -43,7 +33,7 @@
     EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, FenceTime::NO_FENCE), Duration::zero());
 
     FenceToFenceTimeMap fenceMap;
-    const auto [fence, fenceTime] = makePendingFence(fenceMap);
+    const auto [fence, fenceTime] = fenceMap.makePendingFenceForTest();
     EXPECT_EQ(tracker.trackPendingFrame(kCompositeTime, fenceTime), Duration::zero());
 
     fenceTime->signalForTest(9999);
@@ -56,8 +46,9 @@
     PresentLatencyTracker tracker;
 
     FenceToFenceTimeMap fenceMap;
-    std::array<FencePair, PresentLatencyTracker::kMaxPendingFrames> fences;
-    std::generate(fences.begin(), fences.end(), [&fenceMap] { return makePendingFence(fenceMap); });
+    std::array<FenceToFenceTimeMap::FencePair, PresentLatencyTracker::kMaxPendingFrames> fences;
+    std::generate(fences.begin(), fences.end(),
+                  [&fenceMap] { return fenceMap.makePendingFenceForTest(); });
 
     // The present latency is 0 if all fences are pending.
     const TimePoint kCompositeTime = TimePoint::fromNs(1234);
@@ -71,7 +62,7 @@
         fences[i].second->signalForTest(kCompositeTime.ns() + static_cast<nsecs_t>(i));
     }
 
-    const auto fence = makePendingFence(fenceMap);
+    const auto fence = fenceMap.makePendingFenceForTest();
 
     // ...then the present latency is measured using the latest frame.
     constexpr Duration kPresentLatency = Duration::fromNs(static_cast<nsecs_t>(kPresentCount) - 1);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e2e89ad..c6fdcfe 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -77,6 +77,7 @@
 #include <processgroup/processgroup.h>
 #include <renderengine/RenderEngine.h>
 #include <renderengine/impl/ExternalTexture.h>
+#include <scheduler/FrameTargeter.h>
 #include <sys/types.h>
 #include <ui/ColorSpace.h>
 #include <ui/DataspaceUtils.h>
@@ -909,6 +910,29 @@
         ALOGE("Run StartPropertySetThread failed!");
     }
 
+    if (mTransactionTracing) {
+        TransactionTraceWriter::getInstance().setWriterFunction([&](const std::string& prefix,
+                                                                    bool overwrite) {
+            auto writeFn = [&]() {
+                const std::string filename =
+                        TransactionTracing::DIR_NAME + prefix + TransactionTracing::FILE_NAME;
+                if (overwrite) {
+                    std::ifstream file(filename);
+                    if (file.is_open()) {
+                        return;
+                    }
+                }
+                mTransactionTracing->flush();
+                mTransactionTracing->writeToFile(filename);
+            };
+            if (std::this_thread::get_id() == mMainThreadId) {
+                writeFn();
+            } else {
+                mScheduler->schedule(writeFn).get();
+            }
+        });
+    }
+
     ALOGV("Done initializing");
 }
 
@@ -2139,44 +2163,6 @@
     }
 }
 
-bool SurfaceFlinger::wouldPresentEarly(TimePoint frameTime, Period vsyncPeriod) const {
-    const bool isThreeVsyncsAhead = mExpectedPresentTime - frameTime > 2 * vsyncPeriod;
-    return isThreeVsyncsAhead ||
-            getPreviousPresentFence(frameTime, vsyncPeriod)->getSignalTime() !=
-            Fence::SIGNAL_TIME_PENDING;
-}
-
-auto SurfaceFlinger::getPreviousPresentFence(TimePoint frameTime, Period vsyncPeriod) const
-        -> const FenceTimePtr& {
-    const bool isTwoVsyncsAhead = mExpectedPresentTime - frameTime > vsyncPeriod;
-    const size_t i = static_cast<size_t>(isTwoVsyncsAhead);
-    return mPreviousPresentFences[i].fenceTime;
-}
-
-bool SurfaceFlinger::isFencePending(const FenceTimePtr& fence, int graceTimeMs) {
-    ATRACE_CALL();
-    if (fence == FenceTime::NO_FENCE) {
-        return false;
-    }
-
-    const status_t status = fence->wait(graceTimeMs);
-    // This is the same as Fence::Status::Unsignaled, but it saves a getStatus() call,
-    // which calls wait(0) again internally
-    return status == -ETIME;
-}
-
-TimePoint SurfaceFlinger::calculateExpectedPresentTime(TimePoint frameTime) const {
-    const auto& schedule = mScheduler->getVsyncSchedule();
-
-    const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(frameTime);
-    if (mScheduler->vsyncModulator().getVsyncConfig().sfOffset > 0) {
-        return vsyncDeadline;
-    }
-
-    // Inflate the expected present time if we're targeting the next vsync.
-    return vsyncDeadline + schedule->period();
-}
-
 void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) {
     Mutex::Autolock lock(mStateLock);
     if (configureLocked()) {
@@ -2350,75 +2336,15 @@
     return mustComposite;
 }
 
-bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime)
+bool SurfaceFlinger::commit(const scheduler::FrameTarget& pacesetterFrameTarget)
         FTL_FAKE_GUARD(kMainThreadContext) {
-    // The expectedVsyncTime, which was predicted when this frame was scheduled, is normally in the
-    // future relative to frameTime, but may not be for delayed frames. Adjust mExpectedPresentTime
-    // accordingly, but not mScheduledPresentTime.
-    const TimePoint lastScheduledPresentTime = mScheduledPresentTime;
-    mScheduledPresentTime = expectedVsyncTime;
+    const VsyncId vsyncId = pacesetterFrameTarget.vsyncId();
+    ATRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
 
-    // Calculate the expected present time once and use the cached value throughout this frame to
-    // make sure all layers are seeing this same value.
-    mExpectedPresentTime = expectedVsyncTime >= frameTime ? expectedVsyncTime
-                                                          : calculateExpectedPresentTime(frameTime);
-
-    ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(vsyncId),
-                  ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
-                  mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)");
-
-    const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period();
-    const FenceTimePtr& previousPresentFence = getPreviousPresentFence(frameTime, vsyncPeriod);
-
-    // When backpressure propagation is enabled, we want to give a small grace period of 1ms
-    // for the present fence to fire instead of just giving up on this frame to handle cases
-    // where present fence is just about to get signaled.
-    const int graceTimeForPresentFenceMs = static_cast<int>(
-            mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu));
-
-    // Pending frames may trigger backpressure propagation.
-    const TracedOrdinal<bool> framePending = {"PrevFramePending",
-                                              isFencePending(previousPresentFence,
-                                                             graceTimeForPresentFenceMs)};
-
-    // Frame missed counts for metrics tracking.
-    // A frame is missed if the prior frame is still pending. If no longer pending,
-    // then we still count the frame as missed if the predicted present time
-    // was further in the past than when the fence actually fired.
-
-    // Add some slop to correct for drift. This should generally be
-    // smaller than a typical frame duration, but should not be so small
-    // that it reports reasonable drift as a missed frame.
-    const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2;
-    const nsecs_t previousPresentTime = previousPresentFence->getSignalTime();
-    const TracedOrdinal<bool> frameMissed = {"PrevFrameMissed",
-                                             framePending ||
-                                                     (previousPresentTime >= 0 &&
-                                                      (lastScheduledPresentTime.ns() <
-                                                       previousPresentTime - frameMissedSlop))};
-    const TracedOrdinal<bool> hwcFrameMissed = {"PrevHwcFrameMissed",
-                                                frameMissed &&
-                                                        mCompositionCoverage.test(
-                                                                CompositionCoverage::Hwc)};
-
-    const TracedOrdinal<bool> gpuFrameMissed = {"PrevGpuFrameMissed",
-                                                frameMissed &&
-                                                        mCompositionCoverage.test(
-                                                                CompositionCoverage::Gpu)};
-
-    if (frameMissed) {
-        mFrameMissedCount++;
+    if (pacesetterFrameTarget.didMissFrame()) {
         mTimeStats->incrementMissedFrames();
     }
 
-    if (hwcFrameMissed) {
-        mHwcFrameMissedCount++;
-    }
-
-    if (gpuFrameMissed) {
-        mGpuFrameMissedCount++;
-    }
-
     if (mTracingEnabledChanged) {
         mLayerTracingEnabled = mLayerTracing.isEnabled();
         mTracingEnabledChanged = false;
@@ -2427,7 +2353,7 @@
     // If we are in the middle of a mode change and the fence hasn't
     // fired yet just wait for the next commit.
     if (mSetActiveModePending) {
-        if (framePending) {
+        if (pacesetterFrameTarget.isFramePending()) {
             mScheduler->scheduleFrame();
             return false;
         }
@@ -2441,26 +2367,29 @@
         }
     }
 
-    if (framePending) {
-        if (mBackpressureGpuComposition || (hwcFrameMissed && !gpuFrameMissed)) {
+    if (pacesetterFrameTarget.isFramePending()) {
+        if (mBackpressureGpuComposition || pacesetterFrameTarget.didMissHwcFrame()) {
             scheduleCommit(FrameHint::kNone);
             return false;
         }
     }
 
+    const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period();
+
     // Save this once per commit + composite to ensure consistency
     // TODO (b/240619471): consider removing active display check once AOD is fixed
     const auto activeDisplay = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(mActiveDisplayId));
     mPowerHintSessionEnabled = mPowerAdvisor->usePowerHintSession() && activeDisplay &&
             activeDisplay->getPowerMode() == hal::PowerMode::ON;
     if (mPowerHintSessionEnabled) {
-        mPowerAdvisor->setCommitStart(frameTime);
-        mPowerAdvisor->setExpectedPresentTime(mExpectedPresentTime);
+        mPowerAdvisor->setCommitStart(pacesetterFrameTarget.frameBeginTime());
+        mPowerAdvisor->setExpectedPresentTime(pacesetterFrameTarget.expectedPresentTime());
 
         // Frame delay is how long we should have minus how long we actually have.
         const Duration idealSfWorkDuration =
                 mScheduler->vsyncModulator().getVsyncConfig().sfWorkDuration;
-        const Duration frameDelay = idealSfWorkDuration - (mExpectedPresentTime - frameTime);
+        const Duration frameDelay =
+                idealSfWorkDuration - pacesetterFrameTarget.expectedFrameDuration();
 
         mPowerAdvisor->setFrameDelay(frameDelay);
         mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration);
@@ -2480,7 +2409,8 @@
     // Composite if transactions were committed, or if requested by HWC.
     bool mustComposite = mMustComposite.exchange(false);
     {
-        mFrameTimeline->setSfWakeUp(ftl::to_underlying(vsyncId), frameTime.ns(),
+        mFrameTimeline->setSfWakeUp(ftl::to_underlying(vsyncId),
+                                    pacesetterFrameTarget.frameBeginTime().ns(),
                                     Fps::fromPeriodNsecs(vsyncPeriod.ns()));
 
         const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
@@ -2488,10 +2418,11 @@
         if (flushTransactions) {
             updates = flushLifecycleUpdates();
             if (mTransactionTracing) {
-                mTransactionTracing->addCommittedTransactions(ftl::to_underlying(vsyncId),
-                                                              frameTime.ns(), updates,
-                                                              mFrontEndDisplayInfos,
-                                                              mFrontEndDisplayInfosChanged);
+                mTransactionTracing
+                        ->addCommittedTransactions(ftl::to_underlying(vsyncId),
+                                                   pacesetterFrameTarget.frameBeginTime().ns(),
+                                                   updates, mFrontEndDisplayInfos,
+                                                   mFrontEndDisplayInfosChanged);
             }
         }
         bool transactionsAreEmpty;
@@ -2530,11 +2461,11 @@
     }
 
     updateCursorAsync();
-    updateInputFlinger(vsyncId, frameTime);
+    updateInputFlinger(vsyncId, pacesetterFrameTarget.frameBeginTime());
 
     if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
         // This will block and tracing should only be enabled for debugging.
-        addToLayerTracing(mVisibleRegionsDirty, frameTime, vsyncId);
+        addToLayerTracing(mVisibleRegionsDirty, pacesetterFrameTarget.frameBeginTime(), vsyncId);
     }
     mLastCommittedVsyncId = vsyncId;
 
@@ -2543,8 +2474,11 @@
     return mustComposite && CC_LIKELY(mBootStage != BootStage::BOOTLOADER);
 }
 
-void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId)
+CompositeResult SurfaceFlinger::composite(scheduler::FrameTargeter& pacesetterFrameTargeter)
         FTL_FAKE_GUARD(kMainThreadContext) {
+    const scheduler::FrameTarget& pacesetterFrameTarget = pacesetterFrameTargeter.target();
+
+    const VsyncId vsyncId = pacesetterFrameTarget.vsyncId();
     ATRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
 
     compositionengine::CompositionRefreshArgs refreshArgs;
@@ -2552,17 +2486,18 @@
     refreshArgs.outputs.reserve(displays.size());
     std::vector<DisplayId> displayIds;
     for (const auto& [_, display] : displays) {
-        bool dropFrame = false;
-        if (display->isVirtual()) {
-            Fps refreshRate = display->getAdjustedRefreshRate();
-            using fps_approx_ops::operator>;
-            dropFrame = (refreshRate > 0_Hz) && !mScheduler->isVsyncInPhase(frameTime, refreshRate);
-        }
-        if (!dropFrame) {
-            refreshArgs.outputs.push_back(display->getCompositionDisplay());
-        }
-        display->tracePowerMode();
         displayIds.push_back(display->getId());
+        display->tracePowerMode();
+
+        if (display->isVirtual()) {
+            const Fps refreshRate = display->getAdjustedRefreshRate();
+            if (refreshRate.isValid() &&
+                !mScheduler->isVsyncInPhase(pacesetterFrameTarget.frameBeginTime(), refreshRate)) {
+                continue;
+            }
+        }
+
+        refreshArgs.outputs.push_back(display->getCompositionDisplay());
     }
     mPowerAdvisor->setDisplays(displayIds);
 
@@ -2622,15 +2557,15 @@
 
     if (!getHwComposer().getComposer()->isSupported(
                 Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
-        wouldPresentEarly(frameTime, vsyncPeriod)) {
-        const auto prevVsyncTime = mExpectedPresentTime - vsyncPeriod;
+        pacesetterFrameTarget.wouldPresentEarly(vsyncPeriod)) {
         const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
 
-        refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration;
+        refreshArgs.earliestPresentTime =
+                pacesetterFrameTarget.previousFrameVsyncTime(vsyncPeriod) - hwcMinWorkDuration;
     }
 
     refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
-    refreshArgs.expectedPresentTime = mExpectedPresentTime.ns();
+    refreshArgs.expectedPresentTime = pacesetterFrameTarget.expectedPresentTime().ns();
     refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
 
     // Store the present time just before calling to the composition engine so we could notify
@@ -2656,14 +2591,14 @@
         }
     }
 
-    mTimeStats->recordFrameDuration(frameTime.ns(), systemTime());
+    mTimeStats->recordFrameDuration(pacesetterFrameTarget.frameBeginTime().ns(), systemTime());
 
     // Send a power hint after presentation is finished.
     if (mPowerHintSessionEnabled) {
         // Now that the current frame has been presented above, PowerAdvisor needs the present time
         // of the previous frame (whose fence is signaled by now) to determine how long the HWC had
         // waited on that fence to retire before presenting.
-        const auto& previousPresentFence = mPreviousPresentFences[0].fenceTime;
+        const auto& previousPresentFence = pacesetterFrameTarget.presentFenceForPreviousFrame();
 
         mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(previousPresentFence->getSignalTime()),
                                           TimePoint::now());
@@ -2674,7 +2609,7 @@
         scheduleComposite(FrameHint::kNone);
     }
 
-    postComposition(presentTime);
+    postComposition(pacesetterFrameTargeter, presentTime);
 
     const bool hadGpuComposited = mCompositionCoverage.test(CompositionCoverage::Gpu);
     mCompositionCoverage.clear();
@@ -2717,7 +2652,7 @@
     mLayersWithQueuedFrames.clear();
     if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
         // This will block and should only be used for debugging.
-        addToLayerTracing(mVisibleRegionsDirty, frameTime, vsyncId);
+        addToLayerTracing(mVisibleRegionsDirty, pacesetterFrameTarget.frameBeginTime(), vsyncId);
     }
 
     if (mVisibleRegionsDirty) mHdrLayerInfoChanged = true;
@@ -2730,6 +2665,8 @@
     if (mPowerHintSessionEnabled) {
         mPowerAdvisor->setCompositeEnd(TimePoint::now());
     }
+
+    return {mCompositionCoverage};
 }
 
 void SurfaceFlinger::updateLayerGeometry() {
@@ -2813,7 +2750,8 @@
     return ui::ROTATION_0;
 }
 
-void SurfaceFlinger::postComposition(nsecs_t callTime) {
+void SurfaceFlinger::postComposition(scheduler::FrameTargeter& pacesetterFrameTargeter,
+                                     nsecs_t presentStartTime) {
     ATRACE_CALL();
     ALOGV(__func__);
 
@@ -2830,15 +2768,11 @@
         glCompositionDoneFenceTime = FenceTime::NO_FENCE;
     }
 
-    mPreviousPresentFences[1] = mPreviousPresentFences[0];
-
     auto presentFence = defaultDisplay
             ? getHwComposer().getPresentFence(defaultDisplay->getPhysicalId())
             : Fence::NO_FENCE;
 
-    auto presentFenceTime = std::make_shared<FenceTime>(presentFence);
-    mPreviousPresentFences[0] = {presentFence, presentFenceTime};
-
+    auto presentFenceTime = pacesetterFrameTargeter.setPresentFence(presentFence);
     const TimePoint presentTime = TimePoint::now();
 
     // Set presentation information before calling Layer::releasePendingBuffer, such that jank
@@ -3021,7 +2955,7 @@
             if (!layer->hasTrustedPresentationListener()) {
                 return;
             }
-            const frontend::LayerSnapshot* snapshot = (mLayerLifecycleManagerEnabled)
+            const frontend::LayerSnapshot* snapshot = mLayerLifecycleManagerEnabled
                     ? mLayerSnapshotBuilder.getSnapshot(layer->sequence)
                     : layer->getLayerSnapshot();
             std::optional<const DisplayDevice*> displayOpt = std::nullopt;
@@ -3030,7 +2964,8 @@
             }
             const DisplayDevice* display = displayOpt.value_or(nullptr);
             layer->updateTrustedPresentationState(display, snapshot,
-                                                  nanoseconds_to_milliseconds(callTime), false);
+                                                  nanoseconds_to_milliseconds(presentStartTime),
+                                                  false);
         });
     }
 
@@ -3941,6 +3876,9 @@
     if (display->refreshRateSelector().kernelIdleTimerController()) {
         features |= Feature::kKernelIdleTimer;
     }
+    if (mBackpressureGpuComposition) {
+        features |= Feature::kBackpressureGpuComposition;
+    }
 
     auto modulatorPtr = sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs());
 
@@ -4258,33 +4196,38 @@
 
 TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelineCheck(
         const TransactionHandler::TransactionFlushState& flushState) {
-    using TransactionReadiness = TransactionHandler::TransactionReadiness;
     const auto& transaction = *flushState.transaction;
-    TimePoint desiredPresentTime = TimePoint::fromNs(transaction.desiredPresentTime);
+
+    const TimePoint desiredPresentTime = TimePoint::fromNs(transaction.desiredPresentTime);
+    const TimePoint expectedPresentTime = mScheduler->pacesetterFrameTarget().expectedPresentTime();
+
+    using TransactionReadiness = TransactionHandler::TransactionReadiness;
+
     // Do not present if the desiredPresentTime has not passed unless it is more than
     // one second in the future. We ignore timestamps more than 1 second in the future
     // for stability reasons.
-    if (!transaction.isAutoTimestamp && desiredPresentTime >= mExpectedPresentTime &&
-        desiredPresentTime < mExpectedPresentTime + 1s) {
+    if (!transaction.isAutoTimestamp && desiredPresentTime >= expectedPresentTime &&
+        desiredPresentTime < expectedPresentTime + 1s) {
         ATRACE_FORMAT("not current desiredPresentTime: %" PRId64 " expectedPresentTime: %" PRId64,
-                      desiredPresentTime, mExpectedPresentTime);
+                      desiredPresentTime, expectedPresentTime);
         return TransactionReadiness::NotReady;
     }
 
-    if (!mScheduler->isVsyncValid(mExpectedPresentTime, transaction.originUid)) {
-        ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d",
-                      mExpectedPresentTime, transaction.originUid);
+    if (!mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
+        ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime,
+                      transaction.originUid);
         return TransactionReadiness::NotReady;
     }
 
     // If the client didn't specify desiredPresentTime, use the vsyncId to determine the
     // expected present time of this transaction.
     if (transaction.isAutoTimestamp &&
-        frameIsEarly(mExpectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) {
+        frameIsEarly(expectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) {
         ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
-                      transaction.frameTimelineInfo.vsyncId, mExpectedPresentTime);
+                      transaction.frameTimelineInfo.vsyncId, expectedPresentTime);
         return TransactionReadiness::NotReady;
     }
+
     return TransactionReadiness::Ready;
 }
 
@@ -6024,10 +5967,6 @@
     dumpVsync(result);
     result.append("\n");
 
-    StringAppendF(&result, "Total missed frame count: %u\n", mFrameMissedCount.load());
-    StringAppendF(&result, "HWC missed frame count: %u\n", mHwcFrameMissedCount.load());
-    StringAppendF(&result, "GPU missed frame count: %u\n\n", mGpuFrameMissedCount.load());
-
     /*
      * Dump the visible layer list
      */
@@ -6728,7 +6667,7 @@
                         mTransactionTracing->setBufferSize(
                                 TransactionTracing::ACTIVE_TRACING_BUFFER_SIZE);
                     } else {
-                        mTransactionTracing->writeToFile();
+                        TransactionTraceWriter::getInstance().invoke("", /* overwrite= */ true);
                         mTransactionTracing->setBufferSize(
                                 TransactionTracing::CONTINUOUS_TRACING_BUFFER_SIZE);
                     }
@@ -7408,6 +7347,13 @@
                                       renderArea->getHintForSeamlessTransition());
             sdrWhitePointNits = state.sdrWhitePointNits;
             displayBrightnessNits = state.displayBrightnessNits;
+            if (sdrWhitePointNits > 1.0f) {
+                // Restrict the amount of HDR "headroom" in the screenshot to avoid over-dimming
+                // the SDR portion. 2.0 chosen by experimentation
+                constexpr float kMaxScreenshotHeadroom = 2.0f;
+                displayBrightnessNits =
+                        std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, displayBrightnessNits);
+            }
 
             if (requestedDataspace == ui::Dataspace::UNKNOWN) {
                 renderIntent = state.renderIntent;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index b7d2047..143d57f 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -632,8 +632,8 @@
 
     // ICompositor overrides:
     void configure() override;
-    bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) override;
-    void composite(TimePoint frameTime, VsyncId) override;
+    bool commit(const scheduler::FrameTarget&) override;
+    CompositeResult composite(scheduler::FrameTargeter&) override;
     void sample() override;
 
     // ISchedulerCallback overrides:
@@ -952,7 +952,8 @@
     /*
      * Compositing
      */
-    void postComposition(nsecs_t callTime) REQUIRES(kMainThreadContext);
+    void postComposition(scheduler::FrameTargeter&, nsecs_t presentStartTime)
+            REQUIRES(kMainThreadContext);
 
     /*
      * Display management
@@ -993,20 +994,6 @@
      */
     nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock);
 
-    using FenceTimePtr = std::shared_ptr<FenceTime>;
-
-    bool wouldPresentEarly(TimePoint frameTime, Period) const REQUIRES(kMainThreadContext);
-
-    const FenceTimePtr& getPreviousPresentFence(TimePoint frameTime, Period) const
-            REQUIRES(kMainThreadContext);
-
-    // Blocks the thread waiting for up to graceTimeMs in case the fence is about to signal.
-    static bool isFencePending(const FenceTimePtr&, int graceTimeMs);
-
-    // Calculates the expected present time for this frame. For negative offsets, performs a
-    // correction using the predicted vsync for the next frame instead.
-    TimePoint calculateExpectedPresentTime(TimePoint frameTime) const;
-
     /*
      * Display identification
      */
@@ -1252,9 +1239,6 @@
 
     // If blurs should be enabled on this device.
     bool mSupportsBlur = false;
-    std::atomic<uint32_t> mFrameMissedCount = 0;
-    std::atomic<uint32_t> mHwcFrameMissedCount = 0;
-    std::atomic<uint32_t> mGpuFrameMissedCount = 0;
 
     TransactionCallbackInvoker mTransactionCallbackInvoker;
 
@@ -1322,15 +1306,6 @@
     std::unique_ptr<scheduler::RefreshRateStats> mRefreshRateStats;
     scheduler::PresentLatencyTracker mPresentLatencyTracker GUARDED_BY(kMainThreadContext);
 
-    struct FenceWithFenceTime {
-        sp<Fence> fence = Fence::NO_FENCE;
-        FenceTimePtr fenceTime = FenceTime::NO_FENCE;
-    };
-    std::array<FenceWithFenceTime, 2> mPreviousPresentFences;
-
-    TimePoint mScheduledPresentTime GUARDED_BY(kMainThreadContext);
-    TimePoint mExpectedPresentTime GUARDED_BY(kMainThreadContext);
-
     // below flags are set by main thread only
     bool mSetActiveModePending = false;
 
diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING
index 155a275..fc6c4f3 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -1,4 +1,9 @@
 {
+  "imports": [
+    {
+      "path": "frameworks/native/libs/gui"
+    }
+  ],
   "presubmit": [
     {
       "name": "libsurfaceflinger_unittest"
@@ -7,14 +12,6 @@
       "name": "libcompositionengine_test"
     },
     {
-      "name": "libgui_test",
-      "options": [
-        {
-          "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
-        }
-      ]
-    },
-    {
       "name": "libscheduler_test"
     }
   ],
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 632de01..7e330b9 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -28,6 +28,7 @@
 #include "TransactionTracing.h"
 
 namespace android {
+ANDROID_SINGLETON_STATIC_INSTANCE(android::TransactionTraceWriter)
 
 TransactionTracing::TransactionTracing()
       : mProtoParser(std::make_unique<TransactionProtoParser::FlingerDataMapper>()) {
@@ -56,7 +57,7 @@
     writeToFile();
 }
 
-status_t TransactionTracing::writeToFile(std::string filename) {
+status_t TransactionTracing::writeToFile(const std::string& filename) {
     std::scoped_lock lock(mTraceLock);
     proto::TransactionTraceFile fileProto = createTraceFileProto();
     addStartingStateToProtoLocked(fileProto);
@@ -115,6 +116,7 @@
     }
     mPendingUpdates.emplace_back(update);
     tryPushToTracingThread();
+    mLastUpdatedVsyncId = vsyncId;
 }
 
 void TransactionTracing::loop() {
@@ -218,19 +220,29 @@
     mTransactionsAddedToBufferCv.notify_one();
 }
 
-void TransactionTracing::flush(int64_t vsyncId) {
-    while (!mPendingUpdates.empty() || !mPendingDestroyedLayers.empty()) {
-        tryPushToTracingThread();
+void TransactionTracing::flush() {
+    {
+        std::scoped_lock lock(mMainThreadLock);
+        // Collect any pending transactions and wait for transactions to be added to
+        mUpdates.insert(mUpdates.end(), std::make_move_iterator(mPendingUpdates.begin()),
+                        std::make_move_iterator(mPendingUpdates.end()));
+        mPendingUpdates.clear();
+        mDestroyedLayers.insert(mDestroyedLayers.end(), mPendingDestroyedLayers.begin(),
+                                mPendingDestroyedLayers.end());
+        mPendingDestroyedLayers.clear();
+        mTransactionsAvailableCv.notify_one();
     }
     std::unique_lock<std::mutex> lock(mTraceLock);
     base::ScopedLockAssertion assumeLocked(mTraceLock);
-    mTransactionsAddedToBufferCv.wait(lock, [&]() REQUIRES(mTraceLock) {
-        proto::TransactionTraceEntry entry;
-        if (mBuffer.used() > 0) {
-            entry.ParseFromString(mBuffer.back());
-        }
-        return mBuffer.used() > 0 && entry.vsync_id() >= vsyncId;
-    });
+    mTransactionsAddedToBufferCv.wait_for(lock, std::chrono::milliseconds(100),
+                                          [&]() REQUIRES(mTraceLock) {
+                                              proto::TransactionTraceEntry entry;
+                                              if (mBuffer.used() > 0) {
+                                                  entry.ParseFromString(mBuffer.back());
+                                              }
+                                              return mBuffer.used() > 0 &&
+                                                      entry.vsync_id() >= mLastUpdatedVsyncId;
+                                          });
 }
 
 void TransactionTracing::onLayerRemoved(int32_t layerId) {
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index 0e56627..fa006fc 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -19,6 +19,7 @@
 #include <android-base/thread_annotations.h>
 #include <layerproto/TransactionProto.h>
 #include <utils/Errors.h>
+#include <utils/Singleton.h>
 #include <utils/Timers.h>
 
 #include <memory>
@@ -60,10 +61,12 @@
     void addQueuedTransaction(const TransactionState&);
     void addCommittedTransactions(int64_t vsyncId, nsecs_t commitTime, frontend::Update& update,
                                   const frontend::DisplayInfos&, bool displayInfoChanged);
-    status_t writeToFile(std::string filename = FILE_NAME);
+    status_t writeToFile(const std::string& filename = FILE_PATH);
     void setBufferSize(size_t bufferSizeInBytes);
     void onLayerRemoved(int layerId);
     void dump(std::string&) const;
+    // Wait until all the committed transactions for the specified vsync id are added to the buffer.
+    void flush() EXCLUDES(mMainThreadLock);
     static constexpr auto CONTINUOUS_TRACING_BUFFER_SIZE = 512 * 1024;
     static constexpr auto ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024;
     // version 1 - switching to support new frontend
@@ -73,7 +76,9 @@
     friend class TransactionTracingTest;
     friend class SurfaceFlinger;
 
-    static constexpr auto FILE_NAME = "/data/misc/wmtrace/transactions_trace.winscope";
+    static constexpr auto DIR_NAME = "/data/misc/wmtrace/";
+    static constexpr auto FILE_NAME = "/transactions_trace.winscope";
+    static constexpr auto FILE_PATH = "/data/misc/wmtrace/transactions_trace.winscope";
 
     mutable std::mutex mTraceLock;
     RingBuffer<proto::TransactionTraceFile, proto::TransactionTraceEntry> mBuffer
@@ -111,6 +116,7 @@
 
     std::vector<uint32_t /* layerId */> mDestroyedLayers GUARDED_BY(mMainThreadLock);
     std::vector<uint32_t /* layerId */> mPendingDestroyedLayers; // only accessed by main thread
+    int64_t mLastUpdatedVsyncId = -1;
 
     proto::TransactionTraceFile createTraceFileProto() const;
     void loop();
@@ -121,10 +127,21 @@
     void addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) REQUIRES(mTraceLock);
     void updateStartingStateLocked(const proto::TransactionTraceEntry& entry) REQUIRES(mTraceLock);
     // TEST
-    // Wait until all the committed transactions for the specified vsync id are added to the buffer.
-    void flush(int64_t vsyncId) EXCLUDES(mMainThreadLock);
     // Return buffer contents as trace file proto
     proto::TransactionTraceFile writeToProto() EXCLUDES(mMainThreadLock);
 };
 
+class TransactionTraceWriter : public Singleton<TransactionTraceWriter> {
+    friend class Singleton<TransactionTracing>;
+    std::function<void(const std::string& prefix, bool overwrite)> mWriterFunction =
+            [](const std::string&, bool) {};
+
+public:
+    void setWriterFunction(
+            std::function<void(const std::string& prefix, bool overwrite)> function) {
+        mWriterFunction = std::move(function);
+    }
+    void invoke(const std::string& prefix, bool overwrite) { mWriterFunction(prefix, overwrite); }
+};
+
 } // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index 72a11c6..519ef44 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -41,7 +41,7 @@
 using namespace ftl::flag_operators;
 
 bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile,
-                                   const char* outputLayersTracePath) {
+                                   const char* outputLayersTracePath, bool onlyLastEntry) {
     if (traceFile.entry_size() == 0) {
         ALOGD("Trace file is empty");
         return false;
@@ -158,9 +158,11 @@
                                                                   layerTracing.getFlags())
                                           .generate(hierarchyBuilder.getHierarchy());
         auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos);
-        layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(), entry.vsync_id(),
-                            &layersProto, {}, &displayProtos);
-        layerTracing.appendToStream(out);
+        if (!onlyLastEntry || (i == traceFile.entry_size() - 1)) {
+            layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(),
+                                entry.vsync_id(), &layersProto, {}, &displayProtos);
+            layerTracing.appendToStream(out);
+        }
     }
     layerTracing.disable("", /*writeToFile=*/false);
     out.close();
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
index ee1ea6c..e41d1e6 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
@@ -21,6 +21,7 @@
 namespace android {
 class LayerTraceGenerator {
 public:
-    bool generate(const proto::TransactionTraceFile&, const char* outputLayersTracePath);
+    bool generate(const proto::TransactionTraceFile&, const char* outputLayersTracePath,
+                  bool onlyLastEntry);
 };
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp
index c440c19..5ca87e4 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -26,9 +26,9 @@
 using namespace android;
 
 int main(int argc, char** argv) {
-    if (argc > 3) {
+    if (argc > 4) {
         std::cout << "Usage: " << argv[0]
-                  << " [transaction-trace-path] [output-layers-trace-path]\n";
+                  << " [transaction-trace-path] [output-layers-trace-path] [--last-entry-only]\n";
         return -1;
     }
 
@@ -48,12 +48,16 @@
     }
 
     const char* outputLayersTracePath =
-            (argc == 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
-    ;
+            (argc >= 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
+
+    const bool generateLastEntryOnly =
+            argc >= 4 && std::string_view(argv[3]) == "--last-entry-only";
+
     ALOGD("Generating %s...", outputLayersTracePath);
     std::cout << "Generating " << outputLayersTracePath << "\n";
 
-    if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath)) {
+    if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath,
+                                        generateLastEntryOnly)) {
         std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
         return -1;
     }
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 8e208bc..0c9a16b 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -286,8 +286,8 @@
 private:
     // ICompositor overrides:
     void configure() override {}
-    bool commit(TimePoint, VsyncId, TimePoint) override { return false; }
-    void composite(TimePoint, VsyncId) override {}
+    bool commit(const scheduler::FrameTarget&) override { return false; }
+    CompositeResult composite(scheduler::FrameTargeter&) override { return {}; }
     void sample() override {}
 
     // MessageQueue overrides:
@@ -604,7 +604,9 @@
 
             mFlinger->commitTransactions();
             mFlinger->flushTransactionQueues(getFuzzedVsyncId(mFdp));
-            mFlinger->postComposition(systemTime());
+
+            scheduler::FrameTargeter frameTargeter(mFdp.ConsumeBool());
+            mFlinger->postComposition(frameTargeter, mFdp.ConsumeIntegral<nsecs_t>());
         }
 
         mFlinger->setTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
@@ -622,8 +624,6 @@
 
         mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mFdp.ConsumeIntegral<uid_t>());
 
-        mFlinger->calculateExpectedPresentTime({});
-
         mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool());
 
         fuzzDumpsysAndDebug(&mFdp);
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index f17d2e1..b1fd06f 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -19,6 +19,7 @@
 #include <fuzzer/FuzzedDataProvider.h>
 #include <processgroup/sched_policy.h>
 
+#include <scheduler/IVsyncSource.h>
 #include <scheduler/PresentLatencyTracker.h>
 
 #include "Scheduler/OneShotTimer.h"
@@ -42,6 +43,7 @@
                                      (120_Hz).getPeriodNsecs()};
 
 constexpr auto kLayerVoteTypes = ftl::enum_range<scheduler::RefreshRateSelector::LayerVoteType>();
+constexpr auto kCompositionCoverage = ftl::enum_range<CompositionCoverage>();
 
 constexpr PowerMode kPowerModes[] = {PowerMode::ON, PowerMode::DOZE, PowerMode::OFF,
                                      PowerMode::DOZE_SUSPEND, PowerMode::ON_SUSPEND};
@@ -56,6 +58,10 @@
     component->dump(res);
 }
 
+inline sp<Fence> makeFakeFence() {
+    return sp<Fence>::make(memfd_create("fd", MFD_ALLOW_SEALING));
+}
+
 class SchedulerFuzzer {
 public:
     SchedulerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
@@ -65,6 +71,7 @@
     void fuzzRefreshRateSelection();
     void fuzzRefreshRateSelector();
     void fuzzPresentLatencyTracker();
+    void fuzzFrameTargeter();
     void fuzzVSyncModulator();
     void fuzzVSyncPredictor();
     void fuzzVSyncReactor();
@@ -256,13 +263,13 @@
     reactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed);
     reactor.addHwVsyncTimestamp(mFdp.ConsumeIntegral<nsecs_t>() /*newPeriod*/, std::nullopt,
                                 &periodFlushed);
-    sp<Fence> fence = sp<Fence>::make(memfd_create("fd", MFD_ALLOW_SEALING));
-    std::shared_ptr<FenceTime> ft = std::make_shared<FenceTime>(fence);
+
+    const auto fence = std::make_shared<FenceTime>(makeFakeFence());
     vSyncTracker->addVsyncTimestamp(mFdp.ConsumeIntegral<nsecs_t>());
     FenceTime::Snapshot snap(mFdp.ConsumeIntegral<nsecs_t>());
-    ft->applyTrustedSnapshot(snap);
+    fence->applyTrustedSnapshot(snap);
     reactor.setIgnorePresentFences(mFdp.ConsumeBool());
-    reactor.addPresentFence(ft);
+    reactor.addPresentFence(fence);
     dump<scheduler::VSyncReactor>(&reactor, &mFdp);
 }
 
@@ -392,14 +399,45 @@
 
 void SchedulerFuzzer::fuzzPresentLatencyTracker() {
     scheduler::PresentLatencyTracker tracker;
-    tracker.trackPendingFrame(TimePoint::fromNs(mFdp.ConsumeIntegral<nsecs_t>()),
-                              FenceTime::NO_FENCE);
+
+    int i = 5;
+    while (i-- > 0) {
+        tracker.trackPendingFrame(getFuzzedTimePoint(mFdp),
+                                  std::make_shared<FenceTime>(makeFakeFence()));
+    }
+}
+
+void SchedulerFuzzer::fuzzFrameTargeter() {
+    scheduler::FrameTargeter frameTargeter(mFdp.ConsumeBool());
+
+    const struct VsyncSource final : scheduler::IVsyncSource {
+        explicit VsyncSource(FuzzedDataProvider& fuzzer) : fuzzer(fuzzer) {}
+        FuzzedDataProvider& fuzzer;
+
+        Period period() const { return getFuzzedDuration(fuzzer); }
+        TimePoint vsyncDeadlineAfter(TimePoint) const { return getFuzzedTimePoint(fuzzer); }
+    } vsyncSource{mFdp};
+
+    int i = 10;
+    while (i-- > 0) {
+        frameTargeter.beginFrame({.frameBeginTime = getFuzzedTimePoint(mFdp),
+                                  .vsyncId = getFuzzedVsyncId(mFdp),
+                                  .expectedVsyncTime = getFuzzedTimePoint(mFdp),
+                                  .sfWorkDuration = getFuzzedDuration(mFdp)},
+                                 vsyncSource);
+
+        frameTargeter.setPresentFence(makeFakeFence());
+
+        frameTargeter.endFrame(
+                {.compositionCoverage = mFdp.PickValueInArray(kCompositionCoverage.values)});
+    }
 }
 
 void SchedulerFuzzer::process() {
     fuzzRefreshRateSelection();
     fuzzRefreshRateSelector();
     fuzzPresentLatencyTracker();
+    fuzzFrameTargeter();
     fuzzVSyncModulator();
     fuzzVSyncPredictor();
     fuzzVSyncReactor();
diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
index 2b29530..b8a5e79 100644
--- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
+++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
@@ -59,8 +59,8 @@
         std::string actualLayersTracePath =
                 std::string(temp_dir.path) + "/" + expectedLayersFilename + "_actual";
 
-        EXPECT_TRUE(
-                LayerTraceGenerator().generate(mTransactionTrace, actualLayersTracePath.c_str()))
+        EXPECT_TRUE(LayerTraceGenerator().generate(mTransactionTrace, actualLayersTracePath.c_str(),
+                                                   /*onlyLastEntry=*/true))
                 << "Failed to generate layers trace from " << transactionTracePath;
         EXPECT_TRUE(std::filesystem::exists(std::filesystem::path(actualLayersTracePath)));
         parseLayersTraceFromFile(actualLayersTracePath.c_str(), mActualLayersTraceProto);
@@ -86,9 +86,9 @@
 std::vector<std::filesystem::path> TransactionTraceTestSuite::sTransactionTraces{};
 
 struct LayerInfo {
-    int32_t id;
+    uint64_t id;
     std::string name;
-    int32_t parent;
+    uint64_t parent;
     int z;
     uint64_t curr_frame;
     float x;
@@ -119,8 +119,8 @@
 }
 
 struct find_id : std::unary_function<LayerInfo, bool> {
-    int id;
-    find_id(int id) : id(id) {}
+    uint64_t id;
+    find_id(uint64_t id) : id(id) {}
     bool operator()(LayerInfo const& m) const { return m.id == id; }
 };
 
@@ -136,9 +136,9 @@
         touchableRegionBounds = touchableRegion.bounds();
     }
 
-    return {proto.id(),
+    return {static_cast<uint64_t>(proto.id()),
             proto.name(),
-            proto.parent(),
+            static_cast<uint64_t>(proto.parent()),
             proto.z(),
             proto.curr_frame(),
             proto.has_position() ? proto.position().x() : -1,
@@ -150,7 +150,7 @@
 
 static std::vector<LayerInfo> getLayerInfosFromProto(
         android::surfaceflinger::LayersTraceProto& entry) {
-    std::unordered_map<int32_t /* snapshotId*/, int32_t /*layerId*/> snapshotIdToLayerId;
+    std::unordered_map<uint64_t /* snapshotId*/, uint64_t /*layerId*/> snapshotIdToLayerId;
     std::vector<LayerInfo> layers;
     layers.reserve(static_cast<size_t>(entry.layers().layers_size()));
     bool mapSnapshotIdToLayerId = false;
@@ -158,7 +158,12 @@
         auto layer = entry.layers().layers(i);
         LayerInfo layerInfo = getLayerInfoFromProto(layer);
 
-        snapshotIdToLayerId[layerInfo.id] = static_cast<int32_t>(layer.original_id());
+        uint64_t layerId = layerInfo.name.find("(Mirror)") == std::string::npos
+                ? static_cast<uint64_t>(layer.original_id())
+                : static_cast<uint64_t>(layer.original_id()) | 1ull << 63;
+
+        snapshotIdToLayerId[layerInfo.id] = layerId;
+
         if (layer.original_id() != 0) {
             mapSnapshotIdToLayerId = true;
         }
@@ -172,7 +177,7 @@
     for (auto& layer : layers) {
         layer.id = snapshotIdToLayerId[layer.id];
         auto it = snapshotIdToLayerId.find(layer.parent);
-        layer.parent = it == snapshotIdToLayerId.end() ? -1 : it->second;
+        layer.parent = it == snapshotIdToLayerId.end() ? static_cast<uint64_t>(-1) : it->second;
     }
     return layers;
 }
@@ -189,7 +194,6 @@
 
     std::vector<LayerInfo> expectedLayers = getLayerInfosFromProto(expectedLastEntry);
     std::vector<LayerInfo> actualLayers = getLayerInfosFromProto(actualLastEntry);
-    ;
 
     size_t i = 0;
     for (; i < actualLayers.size() && i < expectedLayers.size(); i++) {
@@ -197,9 +201,9 @@
                                find_id(expectedLayers[i].id));
         EXPECT_NE(it, actualLayers.end());
         EXPECT_EQ(expectedLayers[i], *it);
-        ALOGV("Validating %s[%d] parent=%d z=%d frame=%" PRIu64, expectedLayers[i].name.c_str(),
-              expectedLayers[i].id, expectedLayers[i].parent, expectedLayers[i].z,
-              expectedLayers[i].curr_frame);
+        ALOGV("Validating %s[%" PRIu64 "] parent=%" PRIu64 " z=%d frame=%" PRIu64,
+              expectedLayers[i].name.c_str(), expectedLayers[i].id, expectedLayers[i].parent,
+              expectedLayers[i].z, expectedLayers[i].curr_frame);
     }
 
     EXPECT_EQ(expectedLayers.size(), actualLayers.size());
@@ -208,9 +212,9 @@
         for (size_t j = 0; j < actualLayers.size(); j++) {
             if (std::find_if(expectedLayers.begin(), expectedLayers.end(),
                              find_id(actualLayers[j].id)) == expectedLayers.end()) {
-                ALOGD("actualLayers [%d]:%s parent=%d z=%d frame=%" PRIu64, actualLayers[j].id,
-                      actualLayers[j].name.c_str(), actualLayers[j].parent, actualLayers[j].z,
-                      actualLayers[j].curr_frame);
+                ALOGD("actualLayers [%" PRIu64 "]:%s parent=%" PRIu64 " z=%d frame=%" PRIu64,
+                      actualLayers[j].id, actualLayers[j].name.c_str(), actualLayers[j].parent,
+                      actualLayers[j].z, actualLayers[j].curr_frame);
             }
         }
         FAIL();
@@ -220,9 +224,9 @@
         for (size_t j = 0; j < expectedLayers.size(); j++) {
             if (std::find_if(actualLayers.begin(), actualLayers.end(),
                              find_id(expectedLayers[j].id)) == actualLayers.end()) {
-                ALOGD("expectedLayers [%d]:%s parent=%d z=%d frame=%" PRIu64, expectedLayers[j].id,
-                      expectedLayers[j].name.c_str(), expectedLayers[j].parent, expectedLayers[j].z,
-                      expectedLayers[j].curr_frame);
+                ALOGD("expectedLayers [%" PRIu64 "]:%s parent=%" PRIu64 " z=%d frame=%" PRIu64,
+                      expectedLayers[j].id, expectedLayers[j].name.c_str(),
+                      expectedLayers[j].parent, expectedLayers[j].z, expectedLayers[j].curr_frame);
             }
         }
         FAIL();
diff --git a/services/surfaceflinger/tests/tracing/readme.md b/services/surfaceflinger/tests/tracing/readme.md
index 3e80a74..f545a3c 100644
--- a/services/surfaceflinger/tests/tracing/readme.md
+++ b/services/surfaceflinger/tests/tracing/readme.md
@@ -14,7 +14,9 @@
 #### Workflow ####
 Add transaction traces that resulted in front end bugs along
 with the layer trace after fixing the issue. The layer trace
-can be generated by using the layertracegenerator tool. The
+can be generated by using the layertracegenerator tool. Use the
+--last-entry-only flag to generate only the last entry in the
+trace. This will keep the test data to a manageable size. The
 main goal of this test suite is to add regression tests with
 minimal effort.
 
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscope
new file mode 100644
index 0000000..3246453
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscope
new file mode 100644
index 0000000..ecb9431
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 91875cc..359e2ab 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -20,9 +20,10 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <scheduler/interface/ICompositor.h>
+
 #include "FrameTimeline.h"
 #include "Scheduler/MessageQueue.h"
-#include "SurfaceFlinger.h"
 #include "mock/MockVSyncDispatch.h"
 
 namespace android {
@@ -34,8 +35,8 @@
 
 struct NoOpCompositor final : ICompositor {
     void configure() override {}
-    bool commit(TimePoint, VsyncId, TimePoint) override { return false; }
-    void composite(TimePoint, VsyncId) override {}
+    bool commit(const scheduler::FrameTarget&) override { return false; }
+    CompositeResult composite(scheduler::FrameTargeter&) override { return {}; }
     void sample() override {}
 } gNoOpCompositor;
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index a30f7e0..aac11c0 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -173,8 +173,8 @@
 private:
     // ICompositor overrides:
     void configure() override {}
-    bool commit(TimePoint, VsyncId, TimePoint) override { return false; }
-    void composite(TimePoint, VsyncId) override {}
+    bool commit(const scheduler::FrameTarget&) override { return false; }
+    CompositeResult composite(scheduler::FrameTargeter&) override { return {}; }
     void sample() override {}
 };
 
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 945e488..833984f 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -20,6 +20,11 @@
 #include <chrono>
 #include <variant>
 
+#include <ftl/fake_guard.h>
+#include <ftl/match.h>
+#include <gui/ScreenCaptureResults.h>
+#include <ui/DynamicDisplayInfo.h>
+
 #include <compositionengine/Display.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/OutputLayer.h>
@@ -27,11 +32,7 @@
 #include <compositionengine/impl/Display.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/mock/DisplaySurface.h>
-#include <ftl/fake_guard.h>
-#include <ftl/match.h>
-#include <gui/ScreenCaptureResults.h>
 
-#include <ui/DynamicDisplayInfo.h>
 #include "DisplayDevice.h"
 #include "FakeVsyncConfiguration.h"
 #include "FrameTracer/FrameTracer.h"
@@ -44,7 +45,6 @@
 #include "Scheduler/RefreshRateSelector.h"
 #include "StartPropertySetThread.h"
 #include "SurfaceFlinger.h"
-#include "SurfaceFlingerDefaultFactory.h"
 #include "TestableScheduler.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
@@ -360,25 +360,42 @@
         commitTransactionsLocked(eDisplayTransactionNeeded);
     }
 
-    TimePoint commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime) {
-        mFlinger->commit(frameTime, vsyncId, expectedVsyncTime);
-        return frameTime;
+    void commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime,
+                bool composite = false) {
+        constexpr bool kBackpressureGpuComposition = true;
+        scheduler::FrameTargeter frameTargeter(kBackpressureGpuComposition);
+
+        frameTargeter.beginFrame({.frameBeginTime = frameTime,
+                                  .vsyncId = vsyncId,
+                                  .expectedVsyncTime = expectedVsyncTime,
+                                  .sfWorkDuration = 10ms},
+                                 *mScheduler->getVsyncSchedule());
+
+        mFlinger->commit(frameTargeter.target());
+
+        if (composite) {
+            mFlinger->composite(frameTargeter);
+        }
     }
 
-    TimePoint commit(TimePoint frameTime, VsyncId vsyncId) {
-        return commit(frameTime, vsyncId, frameTime + Period(10ms));
+    void commit(TimePoint frameTime, VsyncId vsyncId, bool composite = false) {
+        return commit(frameTime, vsyncId, frameTime + Period(10ms), composite);
     }
 
-    TimePoint commit() {
+    void commit(bool composite = false) {
         const TimePoint frameTime = scheduler::SchedulerClock::now();
-        return commit(frameTime, kVsyncId);
+        commit(frameTime, kVsyncId, composite);
     }
 
     void commitAndComposite(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime) {
-        mFlinger->composite(commit(frameTime, vsyncId, expectedVsyncTime), vsyncId);
+        constexpr bool kComposite = true;
+        commit(frameTime, vsyncId, expectedVsyncTime, kComposite);
     }
 
-    void commitAndComposite() { mFlinger->composite(commit(), kVsyncId); }
+    void commitAndComposite() {
+        constexpr bool kComposite = true;
+        commit(kComposite);
+    }
 
     auto createDisplay(const String8& displayName, bool secure, float requestedRefreshRate = 0.0f) {
         return mFlinger->createDisplay(displayName, secure, requestedRefreshRate);
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 92411a7..809966f 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -37,7 +37,7 @@
     static constexpr size_t SMALL_BUFFER_SIZE = 1024;
     TransactionTracing mTracing;
 
-    void flush(int64_t vsyncId) { mTracing.flush(vsyncId); }
+    void flush() { mTracing.flush(); }
     proto::TransactionTraceFile writeToProto() { return mTracing.writeToProto(); }
 
     proto::TransactionTraceEntry bufferFront() {
@@ -57,7 +57,7 @@
         std::vector<TransactionState> transactions;
         update.transactions.emplace_back(transaction);
         mTracing.addCommittedTransactions(vsyncId, 0, update, {}, false);
-        flush(vsyncId);
+        flush();
     }
 
     void verifyEntry(const proto::TransactionTraceEntry& actualProto,
@@ -116,7 +116,7 @@
     secondUpdate.transactions =
             std::vector<TransactionState>(transactions.begin(), transactions.begin() + 50);
     mTracing.addCommittedTransactions(secondTransactionSetVsyncId, 0, secondUpdate, {}, false);
-    flush(secondTransactionSetVsyncId);
+    flush();
 
     proto::TransactionTraceFile proto = writeToProto();
     ASSERT_EQ(proto.entry().size(), 2);
@@ -158,7 +158,7 @@
             VSYNC_ID_FIRST_LAYER_CHANGE = ++mVsyncId;
             mTracing.addCommittedTransactions(VSYNC_ID_FIRST_LAYER_CHANGE, 0, update, {}, false);
 
-            flush(VSYNC_ID_FIRST_LAYER_CHANGE);
+            flush();
         }
 
         // add transactions that modify the layer state further so we can test that layer state
@@ -178,7 +178,7 @@
             update.transactions.emplace_back(transaction);
             VSYNC_ID_SECOND_LAYER_CHANGE = ++mVsyncId;
             mTracing.addCommittedTransactions(VSYNC_ID_SECOND_LAYER_CHANGE, 0, update, {}, false);
-            flush(VSYNC_ID_SECOND_LAYER_CHANGE);
+            flush();
         }
 
         // remove child layer
@@ -290,7 +290,7 @@
 
             update.transactions.emplace_back(transaction);
             mTracing.addCommittedTransactions(mVsyncId, 0, update, {}, false);
-            flush(mVsyncId);
+            flush();
         }
     }