SurfaceFlinger: throttle applications based on uid

Add the ability for SurfaceFlinger to be able to throttle down to
a divider of the refresh rate (i.e. for 30/45 for 90Hz)

Change-Id: I6bfd6f43ee1f30e771a136c558d8ae9a6d7fbe0f
Test: Manually via 1039 SF backdoor
Bug: 170502573
Bug: 169270763
Bug: 169271059
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index a4f7449..0911712 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -134,13 +134,15 @@
         EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*eventThread, createEventConnection(_, _))
                 .WillOnce(Return(
-                        new EventThreadConnection(eventThread.get(), ResyncCallback(),
+                        new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                  ResyncCallback(),
                                                   ISurfaceComposer::eConfigChangedSuppress)));
 
         EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
                 .WillOnce(Return(
-                        new EventThreadConnection(sfEventThread.get(), ResyncCallback(),
+                        new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                  ResyncCallback(),
                                                   ISurfaceComposer::eConfigChangedSuppress)));
 
         auto vsyncController = std::make_unique<mock::VsyncController>();
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index db05d5a..f0311bd 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -70,12 +70,14 @@
 void DisplayTransactionTest::injectMockScheduler() {
     EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*mEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(mEventThread, ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(mEventThread, /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     EXPECT_CALL(*mSFEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*mSFEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(mSFEventThread, ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(mSFEventThread, /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     mFlinger.setupScheduler(std::unique_ptr<scheduler::VsyncController>(mVsyncController),
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index f680bdb..3aafd45 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -59,9 +59,11 @@
 protected:
     class MockEventThreadConnection : public EventThreadConnection {
     public:
-        MockEventThreadConnection(impl::EventThread* eventThread, ResyncCallback&& resyncCallback,
+        MockEventThreadConnection(impl::EventThread* eventThread, uid_t callingUid,
+                                  ResyncCallback&& resyncCallback,
                                   ISurfaceComposer::ConfigChanged configChanged)
-              : EventThreadConnection(eventThread, std::move(resyncCallback), configChanged) {}
+              : EventThreadConnection(eventThread, callingUid, std::move(resyncCallback),
+                                      configChanged) {}
         MOCK_METHOD1(postEvent, status_t(const DisplayEventReceiver::Event& event));
     };
 
@@ -73,7 +75,8 @@
 
     void createThread(std::unique_ptr<VSyncSource>);
     sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
-                                                   ISurfaceComposer::ConfigChanged configChanged);
+                                                   ISurfaceComposer::ConfigChanged configChanged,
+                                                   uid_t ownerUid = mConnectionUid);
 
     void expectVSyncSetEnabledCallReceived(bool expectedState);
     void expectVSyncSetDurationCallReceived(std::chrono::nanoseconds expectedDuration,
@@ -89,6 +92,7 @@
     void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                       int32_t expectedConfigId,
                                                       nsecs_t expectedVsyncPeriod);
+    void expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t);
 
     AsyncCallRecorder<void (*)(bool)> mVSyncSetEnabledCallRecorder;
     AsyncCallRecorder<void (*)(VSyncSource::Callback*)> mVSyncSetCallbackCallRecorder;
@@ -96,12 +100,18 @@
             mVSyncSetDurationCallRecorder;
     AsyncCallRecorder<void (*)()> mResyncCallRecorder;
     AsyncCallRecorder<void (*)(nsecs_t)> mInterceptVSyncCallRecorder;
+    AsyncCallRecorder<void (*)(nsecs_t, uid_t)> mThrottleVsyncCallRecorder;
     ConnectionEventRecorder mConnectionEventCallRecorder{0};
+    ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
 
     MockVSyncSource* mVSyncSource;
     VSyncSource::Callback* mCallback = nullptr;
     std::unique_ptr<impl::EventThread> mThread;
     sp<MockEventThreadConnection> mConnection;
+    sp<MockEventThreadConnection> mThrottledConnection;
+
+    static constexpr uid_t mConnectionUid = 443;
+    static constexpr uid_t mThrottledConnectionUid = 177;
 };
 
 EventThreadTest::EventThreadTest() {
@@ -124,6 +134,9 @@
     createThread(std::move(vsyncSource));
     mConnection = createConnection(mConnectionEventCallRecorder,
                                    ISurfaceComposer::eConfigChangedDispatch);
+    mThrottledConnection =
+            createConnection(mThrottledConnectionEventCallRecorder,
+                             ISurfaceComposer::eConfigChangedDispatch, mThrottledConnectionUid);
 
     // A display must be connected for VSYNC events to be delivered.
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
@@ -140,9 +153,15 @@
 }
 
 void EventThreadTest::createThread(std::unique_ptr<VSyncSource> source) {
+    const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) {
+        mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid);
+        return (uid == mThrottledConnectionUid);
+    };
+
     mThread = std::make_unique<impl::EventThread>(std::move(source),
                                                   /*tokenManager=*/nullptr,
-                                                  mInterceptVSyncCallRecorder.getInvocable());
+                                                  mInterceptVSyncCallRecorder.getInvocable(),
+                                                  throttleVsync);
 
     // EventThread should register itself as VSyncSource callback.
     mCallback = expectVSyncSetCallbackCallReceived();
@@ -150,10 +169,11 @@
 }
 
 sp<EventThreadTest::MockEventThreadConnection> EventThreadTest::createConnection(
-        ConnectionEventRecorder& recorder, ISurfaceComposer::ConfigChanged configChanged) {
+        ConnectionEventRecorder& recorder, ISurfaceComposer::ConfigChanged configChanged,
+        uid_t ownerUid) {
     sp<MockEventThreadConnection> connection =
-            new MockEventThreadConnection(mThread.get(), mResyncCallRecorder.getInvocable(),
-                                          configChanged);
+            new MockEventThreadConnection(mThread.get(), ownerUid,
+                                          mResyncCallRecorder.getInvocable(), configChanged);
     EXPECT_CALL(*connection, postEvent(_)).WillRepeatedly(Invoke(recorder.getInvocable()));
     return connection;
 }
@@ -183,6 +203,13 @@
     EXPECT_EQ(expectedTimestamp, std::get<0>(args.value()));
 }
 
+void EventThreadTest::expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t uid) {
+    auto args = mThrottleVsyncCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    EXPECT_EQ(expectedTimestamp, std::get<0>(args.value()));
+    EXPECT_EQ(uid, std::get<1>(args.value()));
+}
+
 void EventThreadTest::expectVsyncEventReceivedByConnection(
         const char* name, ConnectionEventRecorder& connectionEventRecorder,
         nsecs_t expectedTimestamp, unsigned expectedCount) {
@@ -267,13 +294,15 @@
     // The interceptor should receive the event, as well as the connection.
     mCallback->onVSyncEvent(123, 456, 789);
     expectInterceptCallReceived(123);
+    expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
 
     // Use the received callback to signal a second vsync event.
-    // The interceptor should receive the event, but the the connection should
+    // The interceptor should receive the event, but the connection should
     // not as it was only interested in the first.
     mCallback->onVSyncEvent(456, 123, 0);
     expectInterceptCallReceived(456);
+    EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
 
     // EventThread should also detect that at this point that it does not need
@@ -323,16 +352,19 @@
     // interceptor, and the connection.
     mCallback->onVSyncEvent(123, 456, 789);
     expectInterceptCallReceived(123);
+    expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
 
     // A second event should go to the same places.
     mCallback->onVSyncEvent(456, 123, 0);
     expectInterceptCallReceived(456);
+    expectThrottleVsyncReceived(123, mConnectionUid);
     expectVsyncEventReceivedByConnection(456, 2u);
 
     // A third event should go to the same places.
     mCallback->onVSyncEvent(789, 777, 111);
     expectInterceptCallReceived(789);
+    expectThrottleVsyncReceived(777, mConnectionUid);
     expectVsyncEventReceivedByConnection(789, 3u);
 }
 
@@ -346,16 +378,19 @@
     mCallback->onVSyncEvent(123, 456, 789);
     expectInterceptCallReceived(123);
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+    EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
 
     // The second event will be seen by the interceptor and the connection.
     mCallback->onVSyncEvent(456, 123, 0);
     expectInterceptCallReceived(456);
     expectVsyncEventReceivedByConnection(456, 2u);
+    EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
 
     // The third event will be seen by the interceptor, and not the connection.
     mCallback->onVSyncEvent(789, 777, 744);
     expectInterceptCallReceived(789);
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+    EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
 
     // The fourth event will be seen by the interceptor and the connection.
     mCallback->onVSyncEvent(101112, 7847, 86);
@@ -408,19 +443,19 @@
 }
 
 TEST_F(EventThreadTest, tracksEventConnections) {
-    EXPECT_EQ(1, mThread->getEventThreadConnectionCount());
+    EXPECT_EQ(2, mThread->getEventThreadConnectionCount());
     ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
     sp<MockEventThreadConnection> errorConnection =
             createConnection(errorConnectionEventRecorder,
                              ISurfaceComposer::eConfigChangedSuppress);
     mThread->setVsyncRate(1, errorConnection);
-    EXPECT_EQ(2, mThread->getEventThreadConnectionCount());
+    EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
     ConnectionEventRecorder secondConnectionEventRecorder{0};
     sp<MockEventThreadConnection> secondConnection =
             createConnection(secondConnectionEventRecorder,
                              ISurfaceComposer::eConfigChangedSuppress);
     mThread->setVsyncRate(1, secondConnection);
-    EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
+    EXPECT_EQ(4, mThread->getEventThreadConnectionCount());
 
     // EventThread should enable vsync callbacks.
     expectVSyncSetEnabledCallReceived(true);
@@ -432,7 +467,7 @@
     expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
     expectVsyncEventReceivedByConnection("successConnection", secondConnectionEventRecorder, 123,
                                          1u);
-    EXPECT_EQ(2, mThread->getEventThreadConnectionCount());
+    EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
 }
 
 TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) {
@@ -514,5 +549,35 @@
     ASSERT_FALSE(args.has_value());
 }
 
+TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) {
+    // Signal that we want the next vsync event to be posted to the throttled connection
+    mThread->requestNextVsync(mThrottledConnection);
+
+    // EventThread should immediately request a resync.
+    EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
+
+    // EventThread should enable vsync callbacks.
+    expectVSyncSetEnabledCallReceived(true);
+
+    // Use the received callback to signal a first vsync event.
+    // The interceptor should receive the event, but not the connection.
+    mCallback->onVSyncEvent(123, 456, 789);
+    expectInterceptCallReceived(123);
+    expectThrottleVsyncReceived(456, mThrottledConnectionUid);
+    mThrottledConnectionEventCallRecorder.waitForUnexpectedCall();
+
+    // Use the received callback to signal a second vsync event.
+    // The interceptor should receive the event, but the connection should
+    // not as it was only interested in the first.
+    mCallback->onVSyncEvent(456, 123, 0);
+    expectInterceptCallReceived(456);
+    expectThrottleVsyncReceived(123, mThrottledConnectionUid);
+    EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+
+    // EventThread should not change the vsync state as it didn't send the event
+    // yet
+    EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 1f6f166..4762fd4 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -1471,6 +1471,34 @@
     EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
 }
 
+TEST_F(RefreshRateConfigsTest, RefreshRateDividerForUnknownUid) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_30);
+    EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDividerForUid(1234));
+}
+
+TEST_F(RefreshRateConfigsTest, RefreshRateDividerForUid) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_30);
+    const uid_t uid = 1234;
+    refreshRateConfigs->setPreferredRefreshRateForUid(uid, 30);
+    EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_60);
+    EXPECT_EQ(2, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_72);
+    EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
+    EXPECT_EQ(3, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_120);
+    EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+}
+
 } // namespace
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
index 2c8178e..8cd8372 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
@@ -131,12 +131,14 @@
 
     EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*eventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(eventThread.get(), ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     auto vsyncController = std::make_unique<mock::VsyncController>();
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index eee9400..509858a 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -42,7 +42,7 @@
     class MockEventThreadConnection : public android::EventThreadConnection {
     public:
         explicit MockEventThreadConnection(EventThread* eventThread)
-              : EventThreadConnection(eventThread, ResyncCallback(),
+              : EventThreadConnection(eventThread, /*callingUid=*/0, ResyncCallback(),
                                       ISurfaceComposer::eConfigChangedSuppress) {}
         ~MockEventThreadConnection() = default;
 
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index efee826..e25d501 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -171,12 +171,14 @@
 
     EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*eventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(eventThread.get(), ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     auto vsyncController = std::make_unique<mock::VsyncController>();
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 760bf65..68cf330 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -66,13 +66,15 @@
         EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*eventThread, createEventConnection(_, _))
                 .WillOnce(Return(
-                        new EventThreadConnection(eventThread.get(), ResyncCallback(),
+                        new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                  ResyncCallback(),
                                                   ISurfaceComposer::eConfigChangedSuppress)));
 
         EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
                 .WillOnce(Return(
-                        new EventThreadConnection(sfEventThread.get(), ResyncCallback(),
+                        new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                  ResyncCallback(),
                                                   ISurfaceComposer::eConfigChangedSuppress)));
 
         EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 1e5139c..0af5f30 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -52,6 +52,7 @@
     void setPeriod(nsecs_t) final {}
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
+    bool isVSyncInPhase(nsecs_t, int) const final { return false; }
     void dump(std::string&) const final {}
 
 private:
@@ -88,6 +89,7 @@
     void setPeriod(nsecs_t) final {}
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
+    bool isVSyncInPhase(nsecs_t, int) const final { return false; }
     void dump(std::string&) const final {}
 
 private:
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 69731fd..72b5396 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -48,6 +48,7 @@
     MOCK_METHOD1(setPeriod, void(nsecs_t));
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
+    MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, int));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index d4cd11d..3d60479 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -59,16 +59,16 @@
 };
 
 TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
-    auto [slope, intercept] = tracker.getVSyncPredictionModel();
+    auto model = tracker.getVSyncPredictionModel();
 
-    EXPECT_THAT(slope, Eq(mPeriod));
-    EXPECT_THAT(intercept, Eq(0));
+    EXPECT_THAT(model.slope, Eq(mPeriod));
+    EXPECT_THAT(model.intercept, Eq(0));
 
     auto const changedPeriod = 2000;
     tracker.setPeriod(changedPeriod);
-    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, Eq(changedPeriod));
-    EXPECT_THAT(intercept, Eq(0));
+    model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, Eq(changedPeriod));
+    EXPECT_THAT(model.intercept, Eq(0));
 }
 
 TEST_F(VSyncPredictorTest, reportsSamplesNeededWhenHasNoDataPoints) {
@@ -264,17 +264,17 @@
     }
 
     auto const mMaxRoundingError = 100;
-    auto [slope, intercept] = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, IsCloseTo(fastPeriod, mMaxRoundingError));
-    EXPECT_THAT(intercept, IsCloseTo(0, mMaxRoundingError));
+    auto model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, IsCloseTo(fastPeriod, mMaxRoundingError));
+    EXPECT_THAT(model.intercept, IsCloseTo(0, mMaxRoundingError));
 
     tracker.setPeriod(slowPeriod);
     for (auto const& timestamp : simulatedVsyncsSlow) {
         tracker.addVsyncTimestamp(timestamp);
     }
-    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, IsCloseTo(slowPeriod, mMaxRoundingError));
-    EXPECT_THAT(intercept, IsCloseTo(0, mMaxRoundingError));
+    model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, IsCloseTo(slowPeriod, mMaxRoundingError));
+    EXPECT_THAT(model.intercept, IsCloseTo(0, mMaxRoundingError));
 }
 
 TEST_F(VSyncPredictorTest, willBeAccurateUsingPriorResultsForRate) {
@@ -296,9 +296,9 @@
     for (auto const& timestamp : simulatedVsyncsFast) {
         tracker.addVsyncTimestamp(timestamp);
     }
-    auto [slope, intercept] = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, Eq(fastPeriod));
-    EXPECT_THAT(intercept, Eq(0));
+    auto model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, Eq(fastPeriod));
+    EXPECT_THAT(model.intercept, Eq(0));
 
     tracker.setPeriod(slowPeriod);
     for (auto const& timestamp : simulatedVsyncsSlow) {
@@ -308,16 +308,16 @@
     // we had a model for 100ns mPeriod before, use that until the new samples are
     // sufficiently built up
     tracker.setPeriod(idealPeriod);
-    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, Eq(fastPeriod));
-    EXPECT_THAT(intercept, Eq(0));
+    model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, Eq(fastPeriod));
+    EXPECT_THAT(model.intercept, Eq(0));
 
     for (auto const& timestamp : simulatedVsyncsFast2) {
         tracker.addVsyncTimestamp(timestamp);
     }
-    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, Eq(fastPeriod2));
-    EXPECT_THAT(intercept, Eq(0));
+    model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, Eq(fastPeriod2));
+    EXPECT_THAT(model.intercept, Eq(0));
 }
 
 TEST_F(VSyncPredictorTest, idealModelPredictionsBeforeRegressionModelIsBuilt) {
@@ -407,11 +407,9 @@
         tracker.addVsyncTimestamp(i * realPeriod);
     }
 
-    EXPECT_THAT(std::get<0>(tracker.getVSyncPredictionModel()),
-                IsCloseTo(realPeriod, mMaxRoundingError));
+    EXPECT_THAT(tracker.getVSyncPredictionModel().slope, IsCloseTo(realPeriod, mMaxRoundingError));
     tracker.resetModel();
-    EXPECT_THAT(std::get<0>(tracker.getVSyncPredictionModel()),
-                IsCloseTo(idealPeriod, mMaxRoundingError));
+    EXPECT_THAT(tracker.getVSyncPredictionModel().slope, IsCloseTo(idealPeriod, mMaxRoundingError));
 }
 
 TEST_F(VSyncPredictorTest, slopeAlwaysValid) {
@@ -450,6 +448,33 @@
     EXPECT_THAT(intercept, Eq(0));
 }
 
+TEST_F(VSyncPredictorTest, isVSyncInPhase) {
+    auto last = mNow;
+    auto const bias = 10;
+    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+        mNow += mPeriod - bias;
+        last = mNow;
+        tracker.addVsyncTimestamp(mNow);
+        mNow += bias;
+    }
+
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod - bias));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod - bias));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 990), Eq(mNow + 2 * mPeriod - bias));
+
+    const auto maxDivider = 5;
+    const auto maxPeriods = 15;
+    for (int divider = 1; divider < maxDivider; divider++) {
+        for (int i = 0; i < maxPeriods; i++) {
+            const bool expectedInPhase = (i % divider) == 0;
+            EXPECT_THAT(expectedInPhase, tracker.isVSyncInPhase(mNow + i * mPeriod - bias, divider))
+                    << "vsync at " << mNow + (i + 1) * mPeriod - bias << " is "
+                    << (expectedInPhase ? "not " : "") << "in phase for divider " << divider;
+        }
+    }
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 0dcaf26..a7568e4 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -42,6 +42,7 @@
     MOCK_METHOD1(setPeriod, void(nsecs_t));
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
+    MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, int));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index 03ddc85..de98025 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -33,6 +33,7 @@
     MOCK_METHOD1(setPeriod, void(nsecs_t));
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
+    MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, int));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };