Merge "libgui: Add wb_consumer_base_owns_bq flag" into main
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index c44e540..ef2fa4d 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -392,7 +392,16 @@
     }
 }
 
-Status ServiceManager::getService(const std::string& name, os::Service* outService) {
+Status ServiceManager::getService(const std::string& name, sp<IBinder>* outBinder) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
+
+    *outBinder = tryGetBinder(name, true);
+    // returns ok regardless of result for legacy reasons
+    return Status::ok();
+}
+
+Status ServiceManager::getService2(const std::string& name, os::Service* outService) {
     SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
             PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 18bae68..0d666c6 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -44,7 +44,8 @@
     ~ServiceManager();
 
     // getService will try to start any services it cannot find
-    binder::Status getService(const std::string& name, os::Service* outService) override;
+    binder::Status getService(const std::string& name, sp<IBinder>* outBinder) override;
+    binder::Status getService2(const std::string& name, os::Service* outService) override;
     binder::Status checkService(const std::string& name, os::Service* outService) override;
     binder::Status addService(const std::string& name, const sp<IBinder>& binder,
                               bool allowIsolated, int32_t dumpPriority) override;
diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp
index 9d22641..95f459f 100644
--- a/cmds/servicemanager/test_sm.cpp
+++ b/cmds/servicemanager/test_sm.cpp
@@ -155,8 +155,11 @@
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
     Service outA;
-    EXPECT_TRUE(sm->getService("foo", &outA).isOk());
+    EXPECT_TRUE(sm->getService2("foo", &outA).isOk());
     EXPECT_EQ(serviceA, outA.get<Service::Tag::binder>());
+    sp<IBinder> outBinderA;
+    EXPECT_TRUE(sm->getService("foo", &outBinderA).isOk());
+    EXPECT_EQ(serviceA, outBinderA);
 
     // serviceA should be overwritten by serviceB
     sp<IBinder> serviceB = getBinder();
@@ -164,8 +167,11 @@
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
     Service outB;
-    EXPECT_TRUE(sm->getService("foo", &outB).isOk());
+    EXPECT_TRUE(sm->getService2("foo", &outB).isOk());
     EXPECT_EQ(serviceB, outB.get<Service::Tag::binder>());
+    sp<IBinder> outBinderB;
+    EXPECT_TRUE(sm->getService("foo", &outBinderB).isOk());
+    EXPECT_EQ(serviceB, outBinderB);
 }
 
 TEST(AddService, NoPermissions) {
@@ -188,16 +194,22 @@
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
     Service out;
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
     EXPECT_EQ(service, out.get<Service::Tag::binder>());
+    sp<IBinder> outBinder;
+    EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
+    EXPECT_EQ(service, outBinder);
 }
 
 TEST(GetService, NonExistant) {
     auto sm = getPermissiveServiceManager();
 
     Service out;
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
     EXPECT_EQ(nullptr, out.get<Service::Tag::binder>());
+    sp<IBinder> outBinder;
+    EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
+    EXPECT_EQ(nullptr, outBinder);
 }
 
 TEST(GetService, NoPermissionsForGettingService) {
@@ -205,7 +217,7 @@
 
     EXPECT_CALL(*access, getCallingContext()).WillRepeatedly(Return(Access::CallingContext{}));
     EXPECT_CALL(*access, canAdd(_, _)).WillOnce(Return(true));
-    EXPECT_CALL(*access, canFind(_, _)).WillOnce(Return(false));
+    EXPECT_CALL(*access, canFind(_, _)).WillRepeatedly(Return(false));
 
     sp<ServiceManager> sm = sp<NiceMock<MockServiceManager>>::make(std::move(access));
 
@@ -214,22 +226,28 @@
 
     Service out;
     // returns nullptr but has OK status for legacy compatibility
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
     EXPECT_EQ(nullptr, out.get<Service::Tag::binder>());
+    sp<IBinder> outBinder;
+    EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
+    EXPECT_EQ(nullptr, outBinder);
 }
 
 TEST(GetService, AllowedFromIsolated) {
     std::unique_ptr<MockAccess> access = std::make_unique<NiceMock<MockAccess>>();
 
     EXPECT_CALL(*access, getCallingContext())
-        // something adds it
-        .WillOnce(Return(Access::CallingContext{}))
-        // next call is from isolated app
-        .WillOnce(Return(Access::CallingContext{
-            .uid = AID_ISOLATED_START,
-        }));
+            // something adds it
+            .WillOnce(Return(Access::CallingContext{}))
+            // next calls is from isolated app
+            .WillOnce(Return(Access::CallingContext{
+                    .uid = AID_ISOLATED_START,
+            }))
+            .WillOnce(Return(Access::CallingContext{
+                    .uid = AID_ISOLATED_START,
+            }));
     EXPECT_CALL(*access, canAdd(_, _)).WillOnce(Return(true));
-    EXPECT_CALL(*access, canFind(_, _)).WillOnce(Return(true));
+    EXPECT_CALL(*access, canFind(_, _)).WillRepeatedly(Return(true));
 
     sp<ServiceManager> sm = sp<NiceMock<MockServiceManager>>::make(std::move(access));
 
@@ -238,20 +256,26 @@
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
 
     Service out;
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
     EXPECT_EQ(service, out.get<Service::Tag::binder>());
+    sp<IBinder> outBinder;
+    EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
+    EXPECT_EQ(service, outBinder);
 }
 
 TEST(GetService, NotAllowedFromIsolated) {
     std::unique_ptr<MockAccess> access = std::make_unique<NiceMock<MockAccess>>();
 
     EXPECT_CALL(*access, getCallingContext())
-        // something adds it
-        .WillOnce(Return(Access::CallingContext{}))
-        // next call is from isolated app
-        .WillOnce(Return(Access::CallingContext{
-            .uid = AID_ISOLATED_START,
-        }));
+            // something adds it
+            .WillOnce(Return(Access::CallingContext{}))
+            // next calls is from isolated app
+            .WillOnce(Return(Access::CallingContext{
+                    .uid = AID_ISOLATED_START,
+            }))
+            .WillOnce(Return(Access::CallingContext{
+                    .uid = AID_ISOLATED_START,
+            }));
     EXPECT_CALL(*access, canAdd(_, _)).WillOnce(Return(true));
 
     // TODO(b/136023468): when security check is first, this should be called first
@@ -264,8 +288,11 @@
 
     Service out;
     // returns nullptr but has OK status for legacy compatibility
-    EXPECT_TRUE(sm->getService("foo", &out).isOk());
+    EXPECT_TRUE(sm->getService2("foo", &out).isOk());
     EXPECT_EQ(nullptr, out.get<Service::Tag::binder>());
+    sp<IBinder> outBinder;
+    EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
+    EXPECT_EQ(nullptr, outBinder);
 }
 
 TEST(ListServices, NoPermissions) {
diff --git a/libs/binder/BackendUnifiedServiceManager.cpp b/libs/binder/BackendUnifiedServiceManager.cpp
index 0bf3cad..54f687b 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -33,10 +33,19 @@
 sp<AidlServiceManager> BackendUnifiedServiceManager::getImpl() {
     return mTheRealServiceManager;
 }
+
 binder::Status BackendUnifiedServiceManager::getService(const ::std::string& name,
-                                                        os::Service* _out) {
+                                                        sp<IBinder>* _aidl_return) {
     os::Service service;
-    binder::Status status = mTheRealServiceManager->getService(name, &service);
+    binder::Status status = getService2(name, &service);
+    *_aidl_return = service.get<os::Service::Tag::binder>();
+    return status;
+}
+
+binder::Status BackendUnifiedServiceManager::getService2(const ::std::string& name,
+                                                         os::Service* _out) {
+    os::Service service;
+    binder::Status status = mTheRealServiceManager->getService2(name, &service);
     toBinderService(service, _out);
     return status;
 }
diff --git a/libs/binder/BackendUnifiedServiceManager.h b/libs/binder/BackendUnifiedServiceManager.h
index 4715be4..f5d7e66 100644
--- a/libs/binder/BackendUnifiedServiceManager.h
+++ b/libs/binder/BackendUnifiedServiceManager.h
@@ -26,7 +26,8 @@
     explicit BackendUnifiedServiceManager(const sp<os::IServiceManager>& impl);
 
     sp<os::IServiceManager> getImpl();
-    binder::Status getService(const ::std::string& name, os::Service* out) override;
+    binder::Status getService(const ::std::string& name, sp<IBinder>* _aidl_return) override;
+    binder::Status getService2(const ::std::string& name, os::Service* out) override;
     binder::Status checkService(const ::std::string& name, os::Service* out) override;
     binder::Status addService(const ::std::string& name, const sp<IBinder>& service,
                               bool allowIsolated, int32_t dumpPriority) override;
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 12a18f2..8b80aed 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -143,7 +143,7 @@
     // mUnifiedServiceManager->getService so that it can be overridden in ServiceManagerHostShim.
     virtual Status realGetService(const std::string& name, sp<IBinder>* _aidl_return) {
         Service service;
-        Status status = mUnifiedServiceManager->getService(name, &service);
+        Status status = mUnifiedServiceManager->getService2(name, &service);
         *_aidl_return = service.get<Service::Tag::binder>();
         return status;
     }
diff --git a/libs/binder/aidl/android/os/IServiceManager.aidl b/libs/binder/aidl/android/os/IServiceManager.aidl
index ac95188..1d1f84f 100644
--- a/libs/binder/aidl/android/os/IServiceManager.aidl
+++ b/libs/binder/aidl/android/os/IServiceManager.aidl
@@ -60,9 +60,23 @@
      * exists for legacy purposes.
      *
      * Returns null if the service does not exist.
+     *
+     * @deprecated TODO(b/355394904): Use getService2 instead.
      */
     @UnsupportedAppUsage
-    Service getService(@utf8InCpp String name);
+    @nullable IBinder getService(@utf8InCpp String name);
+
+    /**
+     * Retrieve an existing service called @a name from the
+     * service manager.
+     *
+     * This is the same as checkService (returns immediately) but
+     * exists for legacy purposes.
+     *
+     * Returns an enum Service that can be of different types. The
+     * enum value is null if the service does not exist.
+     */
+    Service getService2(@utf8InCpp String name);
 
     /**
      * Retrieve an existing service called @a name from the service
diff --git a/libs/binder/servicedispatcher.cpp b/libs/binder/servicedispatcher.cpp
index 201dfbc..be99065 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -118,7 +118,12 @@
 class ServiceManagerProxyToNative : public android::os::BnServiceManager {
 public:
     ServiceManagerProxyToNative(const sp<android::os::IServiceManager>& impl) : mImpl(impl) {}
-    android::binder::Status getService(const std::string&, android::os::Service*) override {
+    android::binder::Status getService(const std::string&,
+                                       android::sp<android::IBinder>*) override {
+        // We can't send BpBinder for regular binder over RPC.
+        return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
+    }
+    android::binder::Status getService2(const std::string&, android::os::Service*) override {
         // We can't send BpBinder for regular binder over RPC.
         return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
     }
diff --git a/libs/gui/OWNERS b/libs/gui/OWNERS
index 5a9644b..b97cee3 100644
--- a/libs/gui/OWNERS
+++ b/libs/gui/OWNERS
@@ -1,6 +1,8 @@
 # Bug component: 1075131
 
+carlosmr@google.com
 chrisforbes@google.com
+jshargo@google.com
 jreck@google.com
 
 file:/services/surfaceflinger/OWNERS
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index f269f10..9d44cc9 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -83,3 +83,11 @@
   bug: "354273690"
   is_fixed_read_only: true
 } # wb_surface_connect_methods
+
+flag {
+  name: "bq_consumer_attach_callback"
+  namespace: "core_graphics"
+  description: "Controls IProducerListener to have consumer side attach callback"
+  bug: "353202582"
+  is_fixed_read_only: true
+} # bq_consumer_attach_callback
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 1aed8a8..93785f6 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -72,7 +72,7 @@
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mAssociatedDeviceType;
     }
-    inline std::optional<DisplayViewport> getAssociatedViewport() const {
+    inline virtual std::optional<DisplayViewport> getAssociatedViewport() const {
         return mAssociatedViewport;
     }
     inline bool hasMic() const { return mHasMic; }
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 2108488..3fc370c 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -25,9 +25,6 @@
 
 namespace android {
 
-class CursorButtonAccumulator;
-class CursorScrollAccumulator;
-
 /* Keeps track of cursor movements. */
 class CursorMotionAccumulator {
 public:
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 846eced..b27d02d 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -17,6 +17,7 @@
 #include "CursorInputMapper.h"
 
 #include <list>
+#include <optional>
 #include <string>
 #include <tuple>
 #include <variant>
@@ -93,38 +94,6 @@
     return v;
 }
 
-/**
- * A fake InputDeviceContext that allows the associated viewport to be specified for the mapper.
- *
- * This is currently necessary because InputMapperUnitTest doesn't register the mappers it creates
- * with the InputDevice object, meaning that InputDevice::isIgnored becomes true, and the input
- * device doesn't set its associated viewport when it's configured.
- *
- * TODO(b/319217713): work out a way to avoid this fake.
- */
-class ViewportFakingInputDeviceContext : public InputDeviceContext {
-public:
-    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
-                                     std::optional<DisplayViewport> viewport)
-          : InputDeviceContext(device, eventHubId), mAssociatedViewport(viewport) {}
-
-    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
-                                     ui::Rotation orientation)
-          : ViewportFakingInputDeviceContext(device, eventHubId,
-                                             createPrimaryViewport(orientation)) {}
-
-    std::optional<DisplayViewport> getAssociatedViewport() const override {
-        return mAssociatedViewport;
-    }
-
-    void setViewport(const std::optional<DisplayViewport>& viewport) {
-        mAssociatedViewport = viewport;
-    }
-
-private:
-    std::optional<DisplayViewport> mAssociatedViewport;
-};
-
 } // namespace
 
 namespace input_flags = com::android::input::flags;
@@ -541,8 +510,9 @@
     // need to be rotated.
     mPropertyMap.addProperty("cursor.mode", "navigation");
     mPropertyMap.addProperty("cursor.orientationAware", "1");
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation90);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation90)));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
@@ -558,8 +528,9 @@
     // Since InputReader works in the un-rotated coordinate space, only devices that are not
     // orientation-aware are affected by display rotation.
     mPropertyMap.addProperty("cursor.mode", "navigation");
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation0);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation0)));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
@@ -570,7 +541,8 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0, -1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1,  1));
 
-    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation90));
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation90)));
     std::list<NotifyArgs> args =
             mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
@@ -583,7 +555,8 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  0, -1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1, -1));
 
-    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation180));
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation180)));
     args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0, -1));
@@ -595,7 +568,8 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1,  1, -1));
 
-    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation270));
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation270)));
     args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  1,  0));
@@ -649,8 +623,8 @@
     mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
     // Set up the secondary display as the display on which the pointer should be shown.
     // The InputDevice is not associated with any display.
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(secondaryViewport));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     std::list<NotifyArgs> args;
     // Ensure input events are generated for the secondary display.
@@ -670,8 +644,8 @@
     mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
     // Set up the primary display as the display on which the pointer should be shown.
     // Associate the InputDevice with the secondary display.
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(secondaryViewport));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     // With PointerChoreographer enabled, there could be a PointerController for the associated
     // display even if it is different from the pointer display. So the mapper should generate an
@@ -1027,8 +1001,8 @@
     mPropertyMap.addProperty("cursor.mode", "pointer");
     DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
     mReaderConfiguration.setDisplayViewports({primaryViewport});
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, primaryViewport);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(primaryViewport));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     std::list<NotifyArgs> args;
 
@@ -1066,9 +1040,8 @@
     mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID);
 
     // Don't associate the device with the display yet.
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID,
-                                                   /*viewport=*/std::nullopt);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(std::nullopt));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     std::list<NotifyArgs> args;
 
@@ -1082,7 +1055,7 @@
     ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), 20.f);
 
     // Now associate the device with the display, and verify that acceleration is disabled.
-    deviceContext.setViewport(primaryViewport);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(primaryViewport));
     args += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
     args.clear();
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index d51c708..5a3d79d 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -198,6 +198,7 @@
           : InputDevice(context, id, generation, identifier) {}
 
     MOCK_METHOD(uint32_t, getSources, (), (const, override));
+    MOCK_METHOD(std::optional<DisplayViewport>, getAssociatedViewport, (), (const));
     MOCK_METHOD(bool, isEnabled, (), ());
 
     MOCK_METHOD(void, dump, (std::string& dump, const std::string& eventHubDevStr), ());
diff --git a/services/inputflinger/tests/JoystickInputMapper_test.cpp b/services/inputflinger/tests/JoystickInputMapper_test.cpp
index ec70192..adebd72 100644
--- a/services/inputflinger/tests/JoystickInputMapper_test.cpp
+++ b/services/inputflinger/tests/JoystickInputMapper_test.cpp
@@ -16,11 +16,13 @@
 
 #include "JoystickInputMapper.h"
 
+#include <list>
 #include <optional>
 
 #include <EventHub.h>
 #include <NotifyArgs.h>
 #include <ftl/flags.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/DisplayViewport.h>
 #include <linux/input-event-codes.h>
@@ -28,76 +30,54 @@
 
 #include "InputMapperTest.h"
 #include "TestConstants.h"
+#include "TestEventMatchers.h"
 
 namespace android {
 
-namespace {
-
 using namespace ftl::flag_operators;
+using testing::ElementsAre;
+using testing::IsEmpty;
+using testing::Return;
+using testing::VariantWith;
 
-} // namespace
-
-class JoystickInputMapperTest : public InputMapperTest {
+class JoystickInputMapperTest : public InputMapperUnitTest {
 protected:
-    static const int32_t RAW_X_MIN;
-    static const int32_t RAW_X_MAX;
-    static const int32_t RAW_Y_MIN;
-    static const int32_t RAW_Y_MAX;
-
-    static constexpr ui::LogicalDisplayId VIRTUAL_DISPLAY_ID = ui::LogicalDisplayId{1};
-    static const char* const VIRTUAL_DISPLAY_UNIQUE_ID;
-
     void SetUp() override {
-        InputMapperTest::SetUp(InputDeviceClass::JOYSTICK | InputDeviceClass::EXTERNAL);
-    }
-    void prepareAxes() {
-        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
-        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
-    }
+        InputMapperUnitTest::SetUp();
+        EXPECT_CALL(mMockEventHub, getDeviceClasses(EVENTHUB_ID))
+                .WillRepeatedly(Return(InputDeviceClass::JOYSTICK | InputDeviceClass::EXTERNAL));
 
-    void processAxis(JoystickInputMapper& mapper, int32_t axis, int32_t value) {
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, axis, value);
-    }
+        // The mapper requests info on all ABS axis IDs, including ones which aren't actually used
+        // (e.g. in the range from 0x0b (ABS_BRAKE) to 0x0f (ABS_HAT0X)), so just return nullopt for
+        // all axes we don't explicitly set up below.
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, testing::_))
+                .WillRepeatedly(Return(std::nullopt));
 
-    void processSync(JoystickInputMapper& mapper) {
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    }
-
-    void prepareVirtualDisplay(ui::Rotation orientation) {
-        setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, /*width=*/400, /*height=*/500, orientation,
-                                     VIRTUAL_DISPLAY_UNIQUE_ID, /*physicalPort=*/std::nullopt,
-                                     ViewportType::VIRTUAL);
+        setupAxis(ABS_X, /*valid=*/true, /*min=*/-32767, /*max=*/32767, /*resolution=*/0);
+        setupAxis(ABS_Y, /*valid=*/true, /*min=*/-32767, /*max=*/32767, /*resolution=*/0);
     }
 };
 
-const int32_t JoystickInputMapperTest::RAW_X_MIN = -32767;
-const int32_t JoystickInputMapperTest::RAW_X_MAX = 32767;
-const int32_t JoystickInputMapperTest::RAW_Y_MIN = -32767;
-const int32_t JoystickInputMapperTest::RAW_Y_MAX = 32767;
-const char* const JoystickInputMapperTest::VIRTUAL_DISPLAY_UNIQUE_ID = "virtual:1";
-
 TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) {
-    prepareAxes();
-    JoystickInputMapper& mapper = constructAndAddMapper<JoystickInputMapper>();
+    DisplayViewport viewport;
+    viewport.displayId = ui::LogicalDisplayId{1};
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(viewport));
+    mMapper = createInputMapper<JoystickInputMapper>(*mDeviceContext,
+                                                     mFakePolicy->getReaderConfiguration());
 
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
-
-    prepareVirtualDisplay(ui::ROTATION_0);
+    std::list<NotifyArgs> out;
 
     // Send an axis event
-    processAxis(mapper, ABS_X, 100);
-    processSync(mapper);
-
-    NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
+    out = process(EV_ABS, ABS_X, 100);
+    ASSERT_THAT(out, IsEmpty());
+    out = process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(out, ElementsAre(VariantWith<NotifyMotionArgs>(WithDisplayId(viewport.displayId))));
 
     // Send another axis event
-    processAxis(mapper, ABS_Y, 100);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
+    out = process(EV_ABS, ABS_Y, 100);
+    ASSERT_THAT(out, IsEmpty());
+    out = process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(out, ElementsAre(VariantWith<NotifyMotionArgs>(WithDisplayId(viewport.displayId))));
 }
 
 } // namespace android
diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
index a796c49..6607bc7 100644
--- a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
+++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
@@ -78,36 +78,6 @@
     return v;
 }
 
-/**
- * A fake InputDeviceContext that allows the associated viewport to be specified for the mapper.
- *
- * This is currently necessary because InputMapperUnitTest doesn't register the mappers it creates
- * with the InputDevice object, meaning that InputDevice::isIgnored becomes true, and the input
- * device doesn't set its associated viewport when it's configured.
- *
- * TODO(b/319217713): work out a way to avoid this fake.
- */
-class ViewportFakingInputDeviceContext : public InputDeviceContext {
-public:
-    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
-                                     std::optional<DisplayViewport> viewport)
-          : InputDeviceContext(device, eventHubId), mAssociatedViewport(viewport) {}
-
-    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId)
-          : ViewportFakingInputDeviceContext(device, eventHubId, createPrimaryViewport()) {}
-
-    std::optional<DisplayViewport> getAssociatedViewport() const override {
-        return mAssociatedViewport;
-    }
-
-    void setViewport(const std::optional<DisplayViewport>& viewport) {
-        mAssociatedViewport = viewport;
-    }
-
-private:
-    std::optional<DisplayViewport> mAssociatedViewport;
-};
-
 } // namespace
 
 namespace vd_flags = android::companion::virtualdevice::flags;
@@ -138,8 +108,8 @@
     mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
 
     // Set up the secondary display as the associated viewport of the mapper.
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
-    mMapper = createInputMapper<RotaryEncoderInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(secondaryViewport));
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     std::list<NotifyArgs> args;
     // Ensure input events are generated for the secondary display.
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 6a67ac5..2e1f938 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -188,12 +188,13 @@
     postMessage(sp<ConfigureHandler>::make(mCompositor));
 }
 
-void MessageQueue::scheduleFrame() {
+void MessageQueue::scheduleFrame(Duration workDurationSlack) {
     SFTRACE_CALL();
 
     std::lock_guard lock(mVsync.mutex);
+    const auto workDuration = Duration(mVsync.workDuration.get() - workDurationSlack);
     mVsync.scheduledFrameTimeOpt =
-            mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
+            mVsync.registration->schedule({.workDuration = workDuration.ns(),
                                            .readyDuration = 0,
                                            .lastVsync = mVsync.lastCallbackTime.ns()});
 }
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index c5fc371..ba1efbe 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -74,7 +74,7 @@
     virtual void postMessage(sp<MessageHandler>&&) = 0;
     virtual void postMessageDelayed(sp<MessageHandler>&&, nsecs_t uptimeDelay) = 0;
     virtual void scheduleConfigure() = 0;
-    virtual void scheduleFrame() = 0;
+    virtual void scheduleFrame(Duration workDurationSlack = Duration::fromNs(0)) = 0;
 
     virtual std::optional<scheduler::ScheduleResult> getScheduledFrameResult() const = 0;
 };
@@ -149,7 +149,7 @@
     void postMessageDelayed(sp<MessageHandler>&&, nsecs_t uptimeDelay) override;
 
     void scheduleConfigure() override;
-    void scheduleFrame() override;
+    void scheduleFrame(Duration workDurationSlack = Duration::fromNs(0)) override;
 
     std::optional<scheduler::ScheduleResult> getScheduledFrameResult() const override;
 };
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index ee7eda1..04491a2 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -452,7 +452,7 @@
 
     const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
     const auto threshold = currentPeriod / 2;
-    const auto minFramePeriod = minFramePeriodLocked().ns();
+    const auto minFramePeriod = minFramePeriodLocked();
 
     auto prev = lastConfirmedPresentTime.ns();
     for (auto& current : mPastExpectedPresentTimes) {
@@ -463,10 +463,10 @@
                                            1e6f);
         }
 
-        const auto minPeriodViolation = current.ns() - prev + threshold < minFramePeriod;
+        const auto minPeriodViolation = current.ns() - prev + threshold < minFramePeriod.ns();
         if (minPeriodViolation) {
             SFTRACE_NAME("minPeriodViolation");
-            current = TimePoint::fromNs(prev + minFramePeriod);
+            current = TimePoint::fromNs(prev + minFramePeriod.ns());
             prev = current.ns();
         } else {
             break;
@@ -477,7 +477,7 @@
         const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime);
         if (phase > 0ns) {
             for (auto& timeline : mTimelines) {
-                timeline.shiftVsyncSequence(phase);
+                timeline.shiftVsyncSequence(phase, minFramePeriod);
             }
             mPastExpectedPresentTimes.clear();
             return phase;
@@ -778,8 +778,15 @@
     return vsyncSequence.seq % divisor == 0;
 }
 
-void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) {
+void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase, Period minFramePeriod) {
     if (mLastVsyncSequence) {
+        const auto renderRate = mRenderRateOpt.value_or(Fps::fromPeriodNsecs(mIdealPeriod.ns()));
+        const auto threshold = mIdealPeriod.ns() / 2;
+        if (renderRate.getPeriodNsecs() - phase.ns() + threshold >= minFramePeriod.ns()) {
+            SFTRACE_FORMAT_INSTANT("Not-Adjusting vsync by %.2f",
+                                   static_cast<float>(phase.ns()) / 1e6f);
+            return;
+        }
         SFTRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast<float>(phase.ns()) / 1e6f);
         mLastVsyncSequence->vsyncTime += phase.ns();
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 9e1c90b..6c8a2f2 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -104,7 +104,7 @@
         void freeze(TimePoint lastVsync);
         std::optional<TimePoint> validUntil() const { return mValidUntil; }
         bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate);
-        void shiftVsyncSequence(Duration phase);
+        void shiftVsyncSequence(Duration phase, Period minFramePeriod);
         void setRenderRate(std::optional<Fps> renderRateOpt) { mRenderRateOpt = renderRateOpt; }
 
         enum class VsyncOnTimeline {
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
index 38cb446..2185bb0 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -55,15 +55,11 @@
 
     std::optional<TimePoint> earliestPresentTime() const { return mEarliestPresentTime; }
 
-    // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details.
-    TimePoint pastVsyncTime(Period minFramePeriod) const;
-
-    // Equivalent to `presentFenceForPastVsync` unless running N VSYNCs ahead.
-    const FenceTimePtr& presentFenceForPreviousFrame() const {
-        return mPresentFences.front().fenceTime;
-    }
+    // Equivalent to `expectedSignaledPresentFence` unless running N VSYNCs ahead.
+    const FenceTimePtr& presentFenceForPreviousFrame() const;
 
     bool isFramePending() const { return mFramePending; }
+    bool wouldBackpressureHwc() const { return mWouldBackpressureHwc; }
     bool didMissFrame() const { return mFrameMissed; }
     bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; }
     FrameTime lastSignaledFrameTime() const { return mLastSignaledFrameTime; }
@@ -72,7 +68,7 @@
     explicit FrameTarget(const std::string& displayLabel);
     ~FrameTarget() = default;
 
-    bool wouldPresentEarly(Period minFramePeriod) const;
+    bool wouldPresentEarly(Period vsyncPeriod, Period minFramePeriod) const;
 
     // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
     TimePoint previousFrameVsyncTime(Period minFramePeriod) const {
@@ -81,8 +77,7 @@
 
     void addFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime,
                   TimePoint expectedPresentTime) {
-        mFenceWithFenceTimes.next() = {std::move(presentFence), presentFenceTime,
-                                       expectedPresentTime};
+        mPresentFences.next() = {std::move(presentFence), presentFenceTime, expectedPresentTime};
     }
 
     VsyncId mVsyncId;
@@ -94,8 +89,9 @@
     TracedOrdinal<bool> mFrameMissed;
     TracedOrdinal<bool> mHwcFrameMissed;
     TracedOrdinal<bool> mGpuFrameMissed;
+    bool mWouldBackpressureHwc = false;
 
-    struct FenceWithFenceTime {
+    struct PresentFence {
         sp<Fence> fence = Fence::NO_FENCE;
         FenceTimePtr fenceTime = FenceTime::NO_FENCE;
         TimePoint expectedPresentTime = TimePoint();
@@ -106,9 +102,10 @@
     // 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).
-    FenceWithFenceTime presentFenceForPastVsync(Period minFramePeriod) const;
-    std::array<FenceWithFenceTime, 2> mPresentFences;
-    utils::RingBuffer<FenceWithFenceTime, 5> mFenceWithFenceTimes;
+    std::pair<bool /* wouldBackpressure */, PresentFence> expectedSignaledPresentFence(
+            Period vsyncPeriod, Period minFramePeriod) const;
+    std::array<PresentFence, 2> mPresentFencesLegacy;
+    utils::RingBuffer<PresentFence, 5> mPresentFences;
 
     FrameTime mLastSignaledFrameTime;
 
@@ -120,19 +117,6 @@
         static_assert(N > 1);
         return expectedFrameDuration() > (N - 1) * minFramePeriod;
     }
-
-    FenceWithFenceTime pastVsyncTimePtr() const {
-        FenceWithFenceTime pastFenceWithFenceTime;
-        for (size_t i = 0; i < mFenceWithFenceTimes.size(); i++) {
-            const auto& fenceWithFenceTime = mFenceWithFenceTimes[i];
-            // TODO(b/354007767) Fix the below condition to avoid frame drop
-            if (fenceWithFenceTime.expectedPresentTime > mFrameBeginTime) {
-                return pastFenceWithFenceTime;
-            }
-            pastFenceWithFenceTime = fenceWithFenceTime;
-        }
-        return pastFenceWithFenceTime;
-    }
 };
 
 // Computes a display's per-frame metrics about past/upcoming targeting of present deadlines.
@@ -155,7 +139,7 @@
 
     void beginFrame(const BeginFrameArgs&, const IVsyncSource&);
 
-    std::optional<TimePoint> computeEarliestPresentTime(Period minFramePeriod,
+    std::optional<TimePoint> computeEarliestPresentTime(Period vsyncPeriod, Period minFramePeriod,
                                                         Duration hwcMinWorkDuration);
 
     // TODO(b/241285191): Merge with FrameTargeter::endFrame.
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
index 1d248fb..8adf2a6 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -18,8 +18,10 @@
 #include <common/trace.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/IVsyncSource.h>
+#include <utils/Log.h>
 
 namespace android::scheduler {
+using namespace std::chrono_literals;
 
 FrameTarget::FrameTarget(const std::string& displayLabel)
       : mFramePending("PrevFramePending " + displayLabel, false),
@@ -27,32 +29,50 @@
         mHwcFrameMissed("PrevHwcFrameMissed " + displayLabel, false),
         mGpuFrameMissed("PrevGpuFrameMissed " + displayLabel, false) {}
 
-TimePoint FrameTarget::pastVsyncTime(Period minFramePeriod) const {
-    // TODO(b/267315508): Generalize to N VSYNCs.
-    const int shift = static_cast<int>(targetsVsyncsAhead<2>(minFramePeriod));
-    return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift);
-}
-
-FrameTarget::FenceWithFenceTime FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const {
-    if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
-        return pastVsyncTimePtr();
+std::pair<bool /* wouldBackpressure */, FrameTarget::PresentFence>
+FrameTarget::expectedSignaledPresentFence(Period vsyncPeriod, Period minFramePeriod) const {
+    if (!FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+        const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(minFramePeriod));
+        return {true, mPresentFencesLegacy[i]};
     }
-    const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(minFramePeriod));
-    return mPresentFences[i];
+
+    bool wouldBackpressure = true;
+    auto expectedPresentTime = mExpectedPresentTime;
+    for (size_t i = mPresentFences.size(); i != 0; --i) {
+        const auto& fence = mPresentFences[i - 1];
+
+        if (fence.expectedPresentTime + minFramePeriod < expectedPresentTime - vsyncPeriod / 2) {
+            wouldBackpressure = false;
+        }
+
+        if (fence.expectedPresentTime <= mFrameBeginTime) {
+            return {wouldBackpressure, fence};
+        }
+
+        expectedPresentTime = fence.expectedPresentTime;
+    }
+    return {wouldBackpressure, PresentFence{}};
 }
 
-bool FrameTarget::wouldPresentEarly(Period minFramePeriod) 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.
-    const bool allowNVsyncs = FlagManager::getInstance().allow_n_vsyncs_in_targeter();
-    if (!allowNVsyncs && targetsVsyncsAhead<3>(minFramePeriod)) {
+bool FrameTarget::wouldPresentEarly(Period vsyncPeriod, Period minFramePeriod) const {
+    if (targetsVsyncsAhead<3>(minFramePeriod)) {
         return true;
     }
 
-    const auto fence = presentFenceForPastVsync(minFramePeriod).fenceTime;
-    return fence->isValid() && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
+    const auto [wouldBackpressure, fence] =
+            expectedSignaledPresentFence(vsyncPeriod, minFramePeriod);
+
+    return !wouldBackpressure ||
+            (fence.fenceTime->isValid() &&
+             fence.fenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING);
+}
+
+const FenceTimePtr& FrameTarget::presentFenceForPreviousFrame() const {
+    if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+        return mPresentFences.back().fenceTime;
+    }
+
+    return mPresentFencesLegacy.front().fenceTime;
 }
 
 void FrameTargeter::beginFrame(const BeginFrameArgs& args, const IVsyncSource& vsyncSource) {
@@ -86,27 +106,39 @@
     }
 
     if (!mSupportsExpectedPresentTime) {
-        mEarliestPresentTime = computeEarliestPresentTime(minFramePeriod, args.hwcMinWorkDuration);
+        mEarliestPresentTime =
+                computeEarliestPresentTime(vsyncPeriod, minFramePeriod, args.hwcMinWorkDuration);
     }
 
     SFTRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId),
                    ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
                    mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)");
 
-    FenceWithFenceTime pastPresentFence = presentFenceForPastVsync(minFramePeriod);
+    const auto [wouldBackpressure, fence] =
+            expectedSignaledPresentFence(vsyncPeriod, minFramePeriod);
 
     // 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));
+    const int graceTimeForPresentFenceMs = [&] {
+        const bool considerBackpressure =
+                mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu);
+
+        if (!FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+            return static_cast<int>(considerBackpressure);
+        }
+
+        if (!wouldBackpressure || !considerBackpressure) {
+            return 0;
+        }
+
+        return static_cast<int>((std::abs(fence.expectedPresentTime.ns() - mFrameBeginTime.ns()) <=
+                                 Duration(1ms).ns()));
+    }();
 
     // Pending frames may trigger backpressure propagation.
     const auto& isFencePending = *isFencePendingFuncPtr;
-    mFramePending = pastPresentFence.fenceTime != FenceTime::NO_FENCE &&
-            isFencePending(pastPresentFence.fenceTime, graceTimeForPresentFenceMs);
+    mFramePending = fence.fenceTime != FenceTime::NO_FENCE &&
+            isFencePending(fence.fenceTime, 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
@@ -114,10 +146,10 @@
     // 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.fenceTime->getSignalTime();
+        const nsecs_t pastPresentTime = fence.fenceTime->getSignalTime();
         if (pastPresentTime < 0) return false;
         mLastSignaledFrameTime = {.signalTime = TimePoint::fromNs(pastPresentTime),
-                                  .expectedPresentTime = pastPresentFence.expectedPresentTime};
+                                  .expectedPresentTime = fence.expectedPresentTime};
         const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2;
         return lastScheduledPresentTime.ns() < pastPresentTime - frameMissedSlop;
     }();
@@ -128,11 +160,14 @@
     if (mFrameMissed) mFrameMissedCount++;
     if (mHwcFrameMissed) mHwcFrameMissedCount++;
     if (mGpuFrameMissed) mGpuFrameMissedCount++;
+
+    mWouldBackpressureHwc = mFramePending && wouldBackpressure;
 }
 
-std::optional<TimePoint> FrameTargeter::computeEarliestPresentTime(Period minFramePeriod,
+std::optional<TimePoint> FrameTargeter::computeEarliestPresentTime(Period vsyncPeriod,
+                                                                   Period minFramePeriod,
                                                                    Duration hwcMinWorkDuration) {
-    if (wouldPresentEarly(minFramePeriod)) {
+    if (wouldPresentEarly(vsyncPeriod, minFramePeriod)) {
         return previousFrameVsyncTime(minFramePeriod) - hwcMinWorkDuration;
     }
     return {};
@@ -151,8 +186,8 @@
     if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
         addFence(std::move(presentFence), presentFenceTime, mExpectedPresentTime);
     } else {
-        mPresentFences[1] = mPresentFences[0];
-        mPresentFences[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime};
+        mPresentFencesLegacy[1] = mPresentFencesLegacy[0];
+        mPresentFencesLegacy[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime};
     }
     return presentFenceTime;
 }
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
index 190d062..6f4e1f1 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -53,12 +53,13 @@
 
     const auto& target() const { return mTargeter.target(); }
 
-    bool wouldPresentEarly(Period minFramePeriod) const {
-        return target().wouldPresentEarly(minFramePeriod);
+    bool wouldPresentEarly(Period vsyncPeriod, Period minFramePeriod) const {
+        return target().wouldPresentEarly(vsyncPeriod, minFramePeriod);
     }
 
-    FrameTarget::FenceWithFenceTime presentFenceForPastVsync(Period minFramePeriod) const {
-        return target().presentFenceForPastVsync(minFramePeriod);
+    std::pair<bool /*wouldBackpressure*/, FrameTarget::PresentFence> expectedSignaledPresentFence(
+            Period vsyncPeriod, Period minFramePeriod) const {
+        return target().expectedSignaledPresentFence(vsyncPeriod, minFramePeriod);
     }
 
     struct Frame {
@@ -173,7 +174,6 @@
 }
 
 TEST_F(FrameTargeterTest, recallsPastVsync) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{111};
     TimePoint frameBeginTime(1000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -181,16 +181,72 @@
     constexpr Duration kFrameDuration = 13ms;
 
     for (int n = 5; n-- > 0;) {
-        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
-        const auto fence = frame.end();
+        FenceTimePtr fence;
+        {
+            Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
+                        kRefreshRate);
+            fence = frame.end();
+        }
 
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - kPeriod);
-        EXPECT_EQ(presentFenceForPastVsync(kPeriod).fenceTime, fence);
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        ASSERT_TRUE(wouldBackpressure);
+        EXPECT_EQ(presentFence.fenceTime, fence);
+    }
+}
+
+TEST_F(FrameTargeterTest, wouldBackpressureAfterTime) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
+    VsyncId vsyncId{111};
+    TimePoint frameBeginTime(1000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 13ms;
+
+    { Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate); }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        EXPECT_TRUE(wouldBackpressure);
+    }
+    {
+        frameBeginTime += kPeriod;
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        EXPECT_FALSE(wouldBackpressure);
+    }
+}
+
+TEST_F(FrameTargeterTest, wouldBackpressureAfterTimeLegacy) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
+    VsyncId vsyncId{111};
+    TimePoint frameBeginTime(1000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 13ms;
+
+    { Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate); }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        EXPECT_TRUE(wouldBackpressure);
+    }
+    {
+        frameBeginTime += kPeriod;
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        EXPECT_TRUE(wouldBackpressure);
     }
 }
 
 TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAhead) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
     constexpr Fps kRefreshRate = 120_Hz;
@@ -198,101 +254,66 @@
     constexpr Duration kFrameDuration = 10ms;
 
     FenceTimePtr previousFence = FenceTime::NO_FENCE;
-
+    FenceTimePtr currentFence = FenceTime::NO_FENCE;
     for (int n = 5; n-- > 0;) {
         Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
-        const auto fence = frame.end();
-
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
-        EXPECT_EQ(presentFenceForPastVsync(kPeriod).fenceTime, previousFence);
-
-        previousFence = fence;
+        EXPECT_EQ(expectedSignaledPresentFence(kPeriod, kPeriod).second.fenceTime, previousFence);
+        previousFence = currentFence;
+        currentFence = frame.end();
     }
 }
 
-TEST_F(FrameTargeterTest, recallsPastNVsyncTwoVsyncsAhead) {
+TEST_F(FrameTargeterTest, recallsPastVsyncFiveVsyncsAhead) {
     SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
+
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
     constexpr Fps kRefreshRate = 120_Hz;
     constexpr Period kPeriod = kRefreshRate.getPeriod();
-    constexpr Duration kFrameDuration = 10ms;
+    constexpr Duration kFrameDuration = 40ms;
 
-    FenceTimePtr previousFence = FenceTime::NO_FENCE;
-
+    FenceTimePtr firstFence = FenceTime::NO_FENCE;
     for (int n = 5; n-- > 0;) {
         Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
         const auto fence = frame.end();
-
-        const auto pastVsyncTime = frameBeginTime + kFrameDuration - 2 * kPeriod;
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), pastVsyncTime);
-        EXPECT_EQ(presentFenceForPastVsync(kFrameDuration).fenceTime, previousFence);
-
-        frameBeginTime += kPeriod;
-        previousFence = fence;
+        if (firstFence == FenceTime::NO_FENCE) {
+            firstFence = fence;
+        }
     }
+
+    Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+    EXPECT_EQ(expectedSignaledPresentFence(kPeriod, kPeriod).second.fenceTime, firstFence);
 }
 
 TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAheadVrr) {
     SET_FLAG_FOR_TEST(flags::vrr_config, true);
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
 
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
     constexpr Fps kRefreshRate = 120_Hz;
-    constexpr Fps kPeakRefreshRate = 240_Hz;
+    constexpr Fps kVsyncRate = 240_Hz;
     constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Period kVsyncPeriod = kVsyncRate.getPeriod();
     constexpr Duration kFrameDuration = 10ms;
 
     FenceTimePtr previousFence = FenceTime::NO_FENCE;
-
+    FenceTimePtr currentFence = FenceTime::NO_FENCE;
     for (int n = 5; n-- > 0;) {
-        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
-                    kPeakRefreshRate);
-        const auto fence = frame.end();
-
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
-        EXPECT_EQ(presentFenceForPastVsync(kPeriod).fenceTime, previousFence);
-
-        previousFence = fence;
-    }
-}
-
-TEST_F(FrameTargeterTest, recallsPastNVsyncTwoVsyncsAheadVrr) {
-    SET_FLAG_FOR_TEST(flags::vrr_config, true);
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
-
-    VsyncId vsyncId{222};
-    TimePoint frameBeginTime(2000ms);
-    constexpr Fps kRefreshRate = 120_Hz;
-    constexpr Fps kPeakRefreshRate = 240_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,
-                    kPeakRefreshRate);
-        const auto fence = frame.end();
-
-        const auto pastVsyncTime = frameBeginTime + kFrameDuration - 2 * kPeriod;
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), pastVsyncTime);
-        EXPECT_EQ(presentFenceForPastVsync(kFrameDuration).fenceTime, previousFence);
-
-        frameBeginTime += kPeriod;
-        previousFence = fence;
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        EXPECT_EQ(expectedSignaledPresentFence(kVsyncPeriod, kPeriod).second.fenceTime,
+                  previousFence);
+        previousFence = currentFence;
+        currentFence = frame.end();
     }
 }
 
 TEST_F(FrameTargeterTest, doesNotDetectEarlyPresentIfNoFence) {
     constexpr Period kPeriod = (60_Hz).getPeriod();
-    EXPECT_EQ(presentFenceForPastVsync(kPeriod).fenceTime, FenceTime::NO_FENCE);
-    EXPECT_FALSE(wouldPresentEarly(kPeriod));
+    EXPECT_EQ(expectedSignaledPresentFence(kPeriod, kPeriod).second.fenceTime, FenceTime::NO_FENCE);
+    EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresent) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{333};
     TimePoint frameBeginTime(3000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -300,20 +321,57 @@
 
     // 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, kRefreshRate);
-        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        {
+            const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        }
+        EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
         EXPECT_FALSE(target().earliestPresentTime());
     }
 
     // The target is early if the past present fence was signaled.
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-    const auto fence = frame.end();
-    fence->signalForTest(frameBeginTime.ns());
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        const auto fence = frame.end();
+        fence->signalForTest(frameBeginTime.ns());
+    }
 
     Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
 
     // `finalFrame` would present early, so it has an earliest present time.
-    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
+    ASSERT_NE(std::nullopt, target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
+}
+
+TEST_F(FrameTargeterTest, detectsEarlyPresentAfterLongPeriod) {
+    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, kRefreshRate);
+        }
+        EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
+        EXPECT_FALSE(target().earliestPresentTime());
+    }
+
+    // The target is early if the past present fence was signaled.
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        const auto fence = frame.end();
+        fence->signalForTest(frameBeginTime.ns());
+    }
+
+    frameBeginTime += 10 * kPeriod;
+
+    Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+
+    // `finalFrame` would present early, so it has an earliest present time.
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
     ASSERT_NE(std::nullopt, target().earliestPresentTime());
     EXPECT_EQ(*target().earliestPresentTime(),
               target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
@@ -322,7 +380,6 @@
 // Same as `detectsEarlyPresent`, above, but verifies that we do not set an earliest present time
 // when there is expected present time support.
 TEST_F(FrameTargeterWithExpectedPresentSupportTest, detectsEarlyPresent) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{333};
     TimePoint frameBeginTime(3000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -330,26 +387,30 @@
 
     // 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, kRefreshRate);
-        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        {
+            const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        }
+        EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
         EXPECT_FALSE(target().earliestPresentTime());
     }
 
     // The target is early if the past present fence was signaled.
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-    const auto fence = frame.end();
-    fence->signalForTest(frameBeginTime.ns());
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+
+        const auto fence = frame.end();
+        fence->signalForTest(frameBeginTime.ns());
+    }
 
     Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
 
     // `finalFrame` would present early, but we have expected present time support, so it has no
     // earliest present time.
-    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
     ASSERT_EQ(std::nullopt, target().earliestPresentTime());
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{444};
     TimePoint frameBeginTime(4000ms);
     constexpr Fps kRefreshRate = 120_Hz;
@@ -357,17 +418,21 @@
 
     // 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, kRefreshRate);
-        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        {
+            const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        }
+        EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
         EXPECT_FALSE(target().earliestPresentTime());
     }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
 
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-    const auto fence = frame.end();
-    fence->signalForTest(frameBeginTime.ns());
+        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(wouldPresentEarly(kPeriod));
+    EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
     EXPECT_FALSE(target().earliestPresentTime());
 
     { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); }
@@ -375,66 +440,21 @@
     Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
 
     // The target is early if the past present fence was signaled.
-    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
     ASSERT_NE(std::nullopt, target().earliestPresentTime());
     EXPECT_EQ(*target().earliestPresentTime(),
               target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
 }
 
-TEST_F(FrameTargeterTest, detectsEarlyPresentNVsyncsAhead) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
-    VsyncId vsyncId{444};
-    TimePoint frameBeginTime(4000ms);
-    Fps refreshRate = 120_Hz;
-    Period period = refreshRate.getPeriod();
-
-    // The target is not early while past present fences are pending.
-    for (int n = 5; n-- > 0;) {
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
-        EXPECT_FALSE(wouldPresentEarly(period));
-        EXPECT_FALSE(target().earliestPresentTime());
-    }
-
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
-    auto fence = frame.end();
-    frameBeginTime += period;
-    fence->signalForTest(frameBeginTime.ns());
-
-    // The target is two VSYNCs ahead, so the past present fence is still pending.
-    EXPECT_FALSE(wouldPresentEarly(period));
-    EXPECT_FALSE(target().earliestPresentTime());
-
-    { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate); }
-
-    Frame oneEarlyPresentFrame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
-    // The target is early if the past present fence was signaled.
-    EXPECT_TRUE(wouldPresentEarly(period));
-    ASSERT_NE(std::nullopt, target().earliestPresentTime());
-    EXPECT_EQ(*target().earliestPresentTime(),
-              target().expectedPresentTime() - period - kHwcMinWorkDuration);
-
-    fence = oneEarlyPresentFrame.end();
-    frameBeginTime += period;
-    fence->signalForTest(frameBeginTime.ns());
-
-    // Change rate to track frame more than 2 vsyncs ahead
-    refreshRate = 144_Hz;
-    period = refreshRate.getPeriod();
-    Frame onePresentEarlyFrame(this, vsyncId++, frameBeginTime, 16ms, refreshRate, refreshRate);
-    // The target is not early as last frame as the past frame is tracked for pending.
-    EXPECT_FALSE(wouldPresentEarly(period));
-}
-
 TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) {
-    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     TimePoint frameBeginTime(5000ms);
     constexpr Fps kRefreshRate = 144_Hz;
     constexpr Period kPeriod = kRefreshRate.getPeriod();
 
-    const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate, kRefreshRate);
+    { const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate, kRefreshRate); }
 
     // The target is more than two VSYNCs ahead, but present fences are not tracked that far back.
-    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
     EXPECT_TRUE(target().earliestPresentTime());
     EXPECT_EQ(*target().earliestPresentTime(),
               target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 22a8993..299f70b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2161,12 +2161,12 @@
     return mScheduler->createDisplayEventConnection(cycle, eventRegistration, layerHandle);
 }
 
-void SurfaceFlinger::scheduleCommit(FrameHint hint) {
+void SurfaceFlinger::scheduleCommit(FrameHint hint, Duration workDurationSlack) {
     if (hint == FrameHint::kActive) {
         mScheduler->resetIdleTimer();
     }
     mPowerAdvisor->notifyDisplayUpdateImminentAndCpuReset();
-    mScheduler->scheduleFrame();
+    mScheduler->scheduleFrame(workDurationSlack);
 }
 
 void SurfaceFlinger::scheduleComposite(FrameHint hint) {
@@ -2620,13 +2620,16 @@
         }
     }
 
-    if (pacesetterFrameTarget.isFramePending()) {
+    if (pacesetterFrameTarget.wouldBackpressureHwc()) {
         if (mBackpressureGpuComposition || pacesetterFrameTarget.didMissHwcFrame()) {
             if (FlagManager::getInstance().vrr_config()) {
                 mScheduler->getVsyncSchedule()->getTracker().onFrameMissed(
                         pacesetterFrameTarget.expectedPresentTime());
             }
-            scheduleCommit(FrameHint::kNone);
+            const Duration slack = FlagManager::getInstance().allow_n_vsyncs_in_targeter()
+                    ? TimePoint::now() - pacesetterFrameTarget.frameBeginTime()
+                    : Duration::fromNs(0);
+            scheduleCommit(FrameHint::kNone, slack);
             return false;
         }
     }
@@ -2913,6 +2916,9 @@
         // 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.
+        // TODO(b/355238809) `presentFenceForPreviousFrame` might not always be signaled (e.g. on
+        // devices
+        //  where HWC does not block on the previous present fence). Revise this assumtion.
         const auto& previousPresentFence = pacesetterTarget.presentFenceForPreviousFrame();
 
         mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(previousPresentFence->getSignalTime()),
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 89ade4e..157b722 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -273,7 +273,7 @@
     enum class FrameHint { kNone, kActive };
 
     // Schedule commit of transactions on the main thread ahead of the next VSYNC.
-    void scheduleCommit(FrameHint);
+    void scheduleCommit(FrameHint, Duration workDurationSlack = Duration::fromNs(0));
     // As above, but also force composite regardless if transactions were committed.
     void scheduleComposite(FrameHint);
     // As above, but also force dirty geometry to repaint.
diff --git a/services/surfaceflinger/Utils/RingBuffer.h b/services/surfaceflinger/Utils/RingBuffer.h
index 198e7b2..215472b 100644
--- a/services/surfaceflinger/Utils/RingBuffer.h
+++ b/services/surfaceflinger/Utils/RingBuffer.h
@@ -43,8 +43,10 @@
     }
 
     T& front() { return (*this)[0]; }
+    const T& front() const { return (*this)[0]; }
 
     T& back() { return (*this)[size() - 1]; }
+    const T& back() const { return (*this)[size() - 1]; }
 
     T& operator[](size_t index) {
         return mBuffer[(static_cast<size_t>(mHead + 1) + index) % mCount];
diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
index 5c742d7..866eb08 100644
--- a/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
@@ -28,6 +28,7 @@
 
 namespace android {
 
+using testing::_;
 using testing::DoAll;
 using testing::Mock;
 using testing::SetArgPointee;
@@ -91,7 +92,7 @@
                          PrintToStringParamName);
 
 TEST_P(FrameRateSelectionStrategyTest, SetAndGet) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
     auto layer = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
@@ -104,7 +105,7 @@
 }
 
 TEST_P(FrameRateSelectionStrategyTest, SetChildOverrideChildren) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
     auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
@@ -128,7 +129,7 @@
 }
 
 TEST_P(FrameRateSelectionStrategyTest, SetParentOverrideChildren) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
     auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
@@ -169,7 +170,7 @@
 }
 
 TEST_P(FrameRateSelectionStrategyTest, OverrideChildrenAndSelf) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
     auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 9899d42..4705dd1 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -35,7 +35,7 @@
 #include "mock/MockVsyncController.h"
 
 namespace android {
-
+using testing::_;
 using testing::DoAll;
 using testing::Mock;
 using testing::SetArgPointee;
@@ -93,7 +93,7 @@
 namespace {
 
 TEST_P(SetFrameRateTest, SetAndGet) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -104,7 +104,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetParent) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -129,7 +129,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetParentAllVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -168,7 +168,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetChild) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -193,7 +193,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetChildAllVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -232,7 +232,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetChildAddAfterVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -262,7 +262,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetChildRemoveAfterVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -293,7 +293,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetParentNotInTree) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -352,7 +352,7 @@
 }
 
 TEST_P(SetFrameRateTest, addChildForParentWithTreeVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     const auto& layerFactory = GetParam();
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
index e5f2a91..2d3ebb4 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
@@ -97,7 +97,7 @@
     // Cleanup conditions
 
     // Creating the display commits a display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 }
 
 TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) {
@@ -129,7 +129,7 @@
     // Cleanup conditions
 
     // Creating the display commits a display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 }
 
 TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForUniqueId) {
@@ -159,7 +159,7 @@
     // Cleanup conditions
 
     // Creating the display commits a display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 }
 
 // Requesting 0 tells SF not to do anything, i.e., default to refresh as physical displays
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
index f8ad8e1..df8f68f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
@@ -38,7 +38,7 @@
     // Call Expectations
 
     // Destroying the display commits a display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     // --------------------------------------------------------------------
     // Invocation
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index 897f9a0..aef467a 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -48,7 +48,7 @@
 
 TEST_F(HotplugTest, schedulesFrameToCommitDisplayTransaction) {
     EXPECT_CALL(*mFlinger.scheduler(), scheduleConfigure()).Times(1);
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     constexpr HWDisplayId displayId1 = 456;
     mFlinger.onComposerHalHotplugEvent(displayId1, DisplayHotplugEvent::DISCONNECTED);
@@ -73,7 +73,7 @@
             .WillOnce(Return(Error::NONE));
 
     // A single commit should be scheduled for both configure calls.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
     mFlinger.configure();
@@ -116,7 +116,7 @@
                 setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
             .WillOnce(Return(Error::NONE));
 
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
     mFlinger.configure();
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
index eaf4684..5231965 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
@@ -28,7 +28,7 @@
 
 TEST_F(InitializeDisplaysTest, initializesDisplays) {
     // Scheduled by the display transaction, and by powering on each display.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(3);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(3);
 
     EXPECT_CALL(static_cast<mock::VSyncTracker&>(
                         mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index 83e2f98..fed7b2e 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -271,7 +271,7 @@
     }
 
     static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) {
-        EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame()).Times(1);
+        EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame(_)).Times(1);
     }
 
     static void setupComposerCallExpectations(DisplayTransactionTest* test, PowerMode mode) {
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index f063809..0814e3d 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -62,7 +62,7 @@
     }
 
     MOCK_METHOD(void, scheduleConfigure, (), (override));
-    MOCK_METHOD(void, scheduleFrame, (), (override));
+    MOCK_METHOD(void, scheduleFrame, (Duration), (override));
     MOCK_METHOD(void, postMessage, (sp<MessageHandler>&&), (override));
 
     void doFrameSignal(ICompositor& compositor, VsyncId vsyncId) {
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index e13fe49..fab1f6d 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -105,7 +105,7 @@
 
     void NotPlacedOnTransactionQueue(uint32_t flags) {
         ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
-        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
         TransactionInfo transaction;
         setupSingle(transaction, flags,
                     /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true,
@@ -129,7 +129,7 @@
 
     void PlaceOnTransactionQueue(uint32_t flags) {
         ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
-        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
         // first check will see desired present time has not passed,
         // but afterwards it will look like the desired present time has passed
@@ -155,7 +155,7 @@
     void BlockedByPriorTransaction(uint32_t flags) {
         ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
         nsecs_t time = systemTime();
-        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(2);
+        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(2);
 
         // transaction that should go on the pending thread
         TransactionInfo transactionA;
@@ -217,7 +217,7 @@
 
 TEST_F(TransactionApplicationTest, AddToPendingQueue) {
     ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     TransactionInfo transactionA; // transaction to go on pending queue
     setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ s2ns(1), false,
@@ -238,7 +238,7 @@
 
 TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) {
     ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     TransactionInfo transactionA; // transaction to go on pending queue
     setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ s2ns(1), false,
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 8690dba..7c678bd 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -1055,6 +1055,36 @@
     EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1400, 1000));
     EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2000, 1000));
 }
+
+TEST_F(VSyncPredictorTest, adjustsOnlyMinFrameViolatingVrrTimeline) {
+    const auto refreshRate = Fps::fromPeriodNsecs(500);
+    auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig{.minFrameIntervalNs =
+                                     static_cast<int32_t>(minFrameRate.getPeriodNsecs())};
+    ftl::NonNull<DisplayModePtr> mode =
+            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfig));
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), mode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000));
+    auto lastConfirmedSignalTime = TimePoint::fromNs(1500);
+    auto lastConfirmedExpectedPresentTime = TimePoint::fromNs(1000);
+    vrrTracker.onFrameBegin(TimePoint::fromNs(2000),
+                            {lastConfirmedSignalTime, lastConfirmedExpectedPresentTime});
+    EXPECT_EQ(3500, vrrTracker.nextAnticipatedVSyncTimeFrom(3000, 1500));
+
+    minFrameRate = Fps::fromPeriodNsecs(2000);
+    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
+    lastConfirmedSignalTime = TimePoint::fromNs(2500);
+    lastConfirmedExpectedPresentTime = TimePoint::fromNs(2500);
+    vrrTracker.onFrameBegin(TimePoint::fromNs(3000),
+                            {lastConfirmedSignalTime, lastConfirmedExpectedPresentTime});
+    // Enough time without adjusting vsync to present with new rate on time, no need of adjustment
+    EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 3500));
+}
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues