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/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index bf2a509..bf5be47 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -31,6 +31,8 @@
 
 #include <android-base/stringprintf.h>
 
+#include <binder/IPCThreadState.h>
+
 #include <cutils/compiler.h>
 #include <cutils/sched_policy.h>
 
@@ -123,11 +125,12 @@
 
 } // namespace
 
-EventThreadConnection::EventThreadConnection(EventThread* eventThread,
+EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
                                              ResyncCallback resyncCallback,
                                              ISurfaceComposer::ConfigChanged configChanged)
       : resyncCallback(std::move(resyncCallback)),
         mConfigChanged(configChanged),
+        mOwnerUid(callingUid),
         mEventThread(eventThread),
         mChannel(gui::BitTube::DefaultSize) {}
 
@@ -170,10 +173,12 @@
 
 EventThread::EventThread(std::unique_ptr<VSyncSource> vsyncSource,
                          android::frametimeline::TokenManager* tokenManager,
-                         InterceptVSyncsCallback interceptVSyncsCallback)
+                         InterceptVSyncsCallback interceptVSyncsCallback,
+                         ThrottleVsyncCallback throttleVsyncCallback)
       : mVSyncSource(std::move(vsyncSource)),
         mTokenManager(tokenManager),
         mInterceptVSyncsCallback(std::move(interceptVSyncsCallback)),
+        mThrottleVsyncCallback(std::move(throttleVsyncCallback)),
         mThreadName(mVSyncSource->getName()) {
     mVSyncSource->setCallback(this);
 
@@ -216,8 +221,9 @@
 
 sp<EventThreadConnection> EventThread::createEventConnection(
         ResyncCallback resyncCallback, ISurfaceComposer::ConfigChanged configChanged) const {
-    return new EventThreadConnection(const_cast<EventThread*>(this), std::move(resyncCallback),
-                                     configChanged);
+    return new EventThreadConnection(const_cast<EventThread*>(this),
+                                     IPCThreadState::self()->getCallingUid(),
+                                     std::move(resyncCallback), configChanged);
 }
 
 status_t EventThread::registerDisplayEventConnection(const sp<EventThreadConnection>& connection) {
@@ -443,6 +449,11 @@
 
 bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event,
                                      const sp<EventThreadConnection>& connection) const {
+    const auto throttleVsync = [&] {
+        return mThrottleVsyncCallback &&
+                mThrottleVsyncCallback(event.vsync.expectedVSyncTimestamp, connection->mOwnerUid);
+    };
+
     switch (event.header.type) {
         case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
             return true;
@@ -458,12 +469,22 @@
                 case VSyncRequest::SingleSuppressCallback:
                     connection->vsyncRequest = VSyncRequest::None;
                     return false;
-                case VSyncRequest::Single:
+                case VSyncRequest::Single: {
+                    if (throttleVsync()) {
+                        return false;
+                    }
                     connection->vsyncRequest = VSyncRequest::SingleSuppressCallback;
                     return true;
+                }
                 case VSyncRequest::Periodic:
+                    if (throttleVsync()) {
+                        return false;
+                    }
                     return true;
                 default:
+                    // We don't throttle vsync if the app set a vsync request rate
+                    // since there is no easy way to do that and this is a very
+                    // rare case
                     return event.vsync.count % vsyncPeriod(connection->vsyncRequest) == 0;
             }
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index e42ca05..2e2d989 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -81,7 +81,7 @@
 
 class EventThreadConnection : public BnDisplayEventConnection {
 public:
-    EventThreadConnection(EventThread*, ResyncCallback,
+    EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback,
                           ISurfaceComposer::ConfigChanged configChanged);
     virtual ~EventThreadConnection();
 
@@ -98,6 +98,8 @@
     const ISurfaceComposer::ConfigChanged mConfigChanged =
             ISurfaceComposer::ConfigChanged::eConfigChangedSuppress;
 
+    const uid_t mOwnerUid;
+
 private:
     virtual void onFirstRef();
     EventThread* const mEventThread;
@@ -143,9 +145,10 @@
 class EventThread : public android::EventThread, private VSyncSource::Callback {
 public:
     using InterceptVSyncsCallback = std::function<void(nsecs_t)>;
+    using ThrottleVsyncCallback = std::function<bool(nsecs_t, uid_t)>;
 
-    EventThread(std::unique_ptr<VSyncSource>, frametimeline::TokenManager*,
-                InterceptVSyncsCallback);
+    EventThread(std::unique_ptr<VSyncSource>, frametimeline::TokenManager*, InterceptVSyncsCallback,
+                ThrottleVsyncCallback);
     ~EventThread();
 
     sp<EventThreadConnection> createEventConnection(
@@ -196,6 +199,7 @@
     frametimeline::TokenManager* const mTokenManager;
 
     const InterceptVSyncsCallback mInterceptVSyncsCallback;
+    const ThrottleVsyncCallback mThrottleVsyncCallback;
     const char* const mThreadName;
 
     std::thread mThread;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 7ab49a9..7d97e72 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -625,4 +625,36 @@
     return RefreshRateConfigs::KernelIdleTimerAction::TurnOn;
 }
 
+void RefreshRateConfigs::setPreferredRefreshRateForUid(uid_t uid, float refreshRateHz) {
+    if (refreshRateHz > 0 && refreshRateHz < 1) {
+        return;
+    }
+
+    std::lock_guard lock(mLock);
+    if (refreshRateHz != 0) {
+        mPreferredRefreshRateForUid[uid] = refreshRateHz;
+    } else {
+        mPreferredRefreshRateForUid.erase(uid);
+    }
+}
+
+int RefreshRateConfigs::getRefreshRateDividerForUid(uid_t uid) const {
+    constexpr float kThreshold = 0.1f;
+    std::lock_guard lock(mLock);
+
+    const auto iter = mPreferredRefreshRateForUid.find(uid);
+    if (iter == mPreferredRefreshRateForUid.end()) {
+        return 1;
+    }
+
+    const auto refreshRateHz = iter->second;
+    const auto numPeriods = mCurrentRefreshRate->getFps() / refreshRateHz;
+    const auto numPeriodsRounded = std::round(numPeriods);
+    if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
+        return 1;
+    }
+
+    return static_cast<int>(numPeriods);
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 280ed62..5cf7d07 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -311,6 +311,13 @@
     // refresh rates.
     KernelIdleTimerAction getIdleTimerAction() const;
 
+    // Stores the preferred refresh rate that an app should run at.
+    // refreshRate == 0 means no preference.
+    void setPreferredRefreshRateForUid(uid_t, float refreshRateHz) EXCLUDES(mLock);
+
+    // Returns a divider for the current refresh rate
+    int getRefreshRateDividerForUid(uid_t) const EXCLUDES(mLock);
+
 private:
     friend class RefreshRateConfigsTest;
 
@@ -368,6 +375,8 @@
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
 
+    std::unordered_map<uid_t, float> mPreferredRefreshRateForUid GUARDED_BY(mLock);
+
     // The min and max refresh rates supported by the device.
     // This will not change at runtime.
     const RefreshRate* mMinSupportedRefreshRate;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 7b8448f..a14019e 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -212,13 +212,26 @@
                                                        readyDuration, traceVsync, name);
 }
 
+bool Scheduler::isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const {
+    const auto divider = mRefreshRateConfigs.getRefreshRateDividerForUid(uid);
+    if (divider <= 1) {
+        return true;
+    }
+
+    return mVsyncSchedule.tracker->isVSyncInPhase(expectedVsyncTimestamp, divider);
+}
+
 Scheduler::ConnectionHandle Scheduler::createConnection(
         const char* connectionName, frametimeline::TokenManager* tokenManager,
         std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,
         impl::EventThread::InterceptVSyncsCallback interceptCallback) {
     auto vsyncSource = makePrimaryDispSyncSource(connectionName, workDuration, readyDuration);
+    auto throttleVsync = [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
+        return !isVsyncValid(expectedVsyncTimestamp, uid);
+    };
     auto eventThread = std::make_unique<impl::EventThread>(std::move(vsyncSource), tokenManager,
-                                                           std::move(interceptCallback));
+                                                           std::move(interceptCallback),
+                                                           std::move(throttleVsync));
     return createConnection(std::move(eventThread));
 }
 
@@ -379,7 +392,8 @@
         auto eventThread =
                 std::make_unique<impl::EventThread>(std::move(vsyncSource),
                                                     /*tokenManager=*/nullptr,
-                                                    impl::EventThread::InterceptVSyncsCallback());
+                                                    impl::EventThread::InterceptVSyncsCallback(),
+                                                    impl::EventThread::ThrottleVsyncCallback());
 
         mInjectorConnectionHandle = createConnection(std::move(eventThread));
     }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 47ce4a4..4c86d26 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -139,6 +139,10 @@
 
     scheduler::VSyncDispatch& getVsyncDispatch() { return *mVsyncSchedule.dispatch; }
 
+    // Returns true if a given vsync timestamp is considered valid vsync
+    // for a given uid
+    bool isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const;
+
     void dump(std::string&) const;
     void dump(ConnectionHandle, std::string&) const;
     void dumpVsync(std::string&) const;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index e90edf7..75d1e6f 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -27,6 +27,9 @@
 #include <chrono>
 #include <sstream>
 
+#undef LOG_TAG
+#define LOG_TAG "VSyncPredictor"
+
 namespace android::scheduler {
 using base::StringAppendF;
 
@@ -66,7 +69,7 @@
 
 nsecs_t VSyncPredictor::currentPeriod() const {
     std::lock_guard lock(mMutex);
-    return std::get<0>(mRateMap.find(mIdealPeriod)->second);
+    return mRateMap.find(mIdealPeriod)->second.slope;
 }
 
 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
@@ -118,7 +121,7 @@
     // normalizing to the oldest timestamp cuts down on error in calculating the intercept.
     auto const oldest_ts = *std::min_element(mTimestamps.begin(), mTimestamps.end());
     auto it = mRateMap.find(mIdealPeriod);
-    auto const currentPeriod = std::get<0>(it->second);
+    auto const currentPeriod = it->second.slope;
     // TODO (b/144707443): its important that there's some precision in the mean of the ordinals
     //                     for the intercept calculation, so scale the ordinals by 1000 to continue
     //                     fixed point calculation. Explore expanding
@@ -172,10 +175,8 @@
     return true;
 }
 
-nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
-    std::lock_guard lock(mMutex);
-
-    auto const [slope, intercept] = getVSyncPredictionModel(lock);
+nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const {
+    auto const [slope, intercept] = getVSyncPredictionModelLocked();
 
     if (mTimestamps.empty()) {
         traceInt64If("VSP-mode", 1);
@@ -210,13 +211,71 @@
     return prediction;
 }
 
-std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel() const {
+nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
     std::lock_guard lock(mMutex);
-    return VSyncPredictor::getVSyncPredictionModel(lock);
+    return nextAnticipatedVSyncTimeFromLocked(timePoint);
 }
 
-std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel(
-        std::lock_guard<std::mutex> const&) const {
+/*
+ * Returns whether a given vsync timestamp is in phase with a vsync divider.
+ * For example, if the vsync timestamps are (0,16,32,48):
+ * isVSyncInPhase(0, 2) = true
+ * isVSyncInPhase(16, 2) = false
+ * isVSyncInPhase(32, 2) = true
+ */
+bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, int divider) const {
+    struct VsyncError {
+        nsecs_t vsyncTimestamp;
+        float error;
+
+        bool operator<(const VsyncError& other) const { return error < other.error; }
+    };
+
+    std::lock_guard lock(mMutex);
+    if (divider <= 1) {
+        return true;
+    }
+
+    const nsecs_t period = mRateMap[mIdealPeriod].slope;
+    const nsecs_t justBeforeTimePoint = timePoint - period / 2;
+    const nsecs_t dividedPeriod = mIdealPeriod / divider;
+
+    // If this is the first time we have asked about this divider with the
+    // current vsync period, it is considered in phase and we store the closest
+    // vsync timestamp
+    const auto knownTimestampIter = mRateDividerKnownTimestampMap.find(dividedPeriod);
+    if (knownTimestampIter == mRateDividerKnownTimestampMap.end()) {
+        const auto vsync = nextAnticipatedVSyncTimeFromLocked(justBeforeTimePoint);
+        mRateDividerKnownTimestampMap[dividedPeriod] = vsync;
+        return true;
+    }
+
+    // Find the next N vsync timestamp where N is the divider.
+    // One of these vsyncs will be in phase. We return the one which is
+    // the most aligned with the last known in phase vsync
+    std::vector<VsyncError> vsyncs(static_cast<size_t>(divider));
+    const nsecs_t knownVsync = knownTimestampIter->second;
+    nsecs_t point = justBeforeTimePoint;
+    for (size_t i = 0; i < divider; i++) {
+        const nsecs_t vsync = nextAnticipatedVSyncTimeFromLocked(point);
+        const auto numPeriods = static_cast<float>(vsync - knownVsync) / (period * divider);
+        const auto error = std::abs(std::round(numPeriods) - numPeriods);
+        vsyncs[i] = {vsync, error};
+        point = vsync + 1;
+    }
+
+    const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end());
+    mRateDividerKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp;
+    return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2;
+}
+
+VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
+    std::lock_guard lock(mMutex);
+    const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
+    return {model.slope, model.intercept};
+}
+
+VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
     return mRateMap.find(mIdealPeriod)->second;
 }
 
@@ -269,8 +328,8 @@
     for (const auto& [idealPeriod, periodInterceptTuple] : mRateMap) {
         StringAppendF(&result,
                       "\t\tFor ideal period %.2fms: period = %.2fms, intercept = %" PRId64 "\n",
-                      idealPeriod / 1e6f, std::get<0>(periodInterceptTuple) / 1e6f,
-                      std::get<1>(periodInterceptTuple));
+                      idealPeriod / 1e6f, periodInterceptTuple.slope / 1e6f,
+                      periodInterceptTuple.intercept);
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 5f2ec49..381cf81 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -38,10 +38,10 @@
                    uint32_t outlierTolerancePercent);
     ~VSyncPredictor();
 
-    bool addVsyncTimestamp(nsecs_t timestamp) final;
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final;
-    nsecs_t currentPeriod() const final;
-    void resetModel() final;
+    bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final EXCLUDES(mMutex);
+    nsecs_t currentPeriod() const final EXCLUDES(mMutex);
+    void resetModel() final EXCLUDES(mMutex);
 
     /*
      * Inform the model that the period is anticipated to change to a new value.
@@ -50,16 +50,23 @@
      *
      * \param [in] period   The new period that should be used.
      */
-    void setPeriod(nsecs_t period) final;
+    void setPeriod(nsecs_t period) final EXCLUDES(mMutex);
 
     /* Query if the model is in need of more samples to make a prediction.
      * \return  True, if model would benefit from more samples, False if not.
      */
-    bool needsMoreSamples() const final;
+    bool needsMoreSamples() const final EXCLUDES(mMutex);
 
-    std::tuple<nsecs_t /* slope */, nsecs_t /* intercept */> getVSyncPredictionModel() const;
+    struct Model {
+        nsecs_t slope;
+        nsecs_t intercept;
+    };
 
-    void dump(std::string& result) const final;
+    VSyncPredictor::Model getVSyncPredictionModel() const EXCLUDES(mMutex);
+
+    bool isVSyncInPhase(nsecs_t timePoint, int divider) const final EXCLUDES(mMutex);
+
+    void dump(std::string& result) const final EXCLUDES(mMutex);
 
 private:
     VSyncPredictor(VSyncPredictor const&) = delete;
@@ -76,13 +83,19 @@
     std::mutex mutable mMutex;
     size_t next(size_t i) const REQUIRES(mMutex);
     bool validate(nsecs_t timestamp) const REQUIRES(mMutex);
-    std::tuple<nsecs_t, nsecs_t> getVSyncPredictionModel(std::lock_guard<std::mutex> const&) const
-            REQUIRES(mMutex);
+
+    Model getVSyncPredictionModelLocked() const REQUIRES(mMutex);
+
+    nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex);
 
     nsecs_t mIdealPeriod GUARDED_BY(mMutex);
     std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);
 
-    std::unordered_map<nsecs_t, std::tuple<nsecs_t, nsecs_t>> mutable mRateMap GUARDED_BY(mMutex);
+    // Map between ideal vsync period and the calculated model
+    std::unordered_map<nsecs_t, Model> mutable mRateMap GUARDED_BY(mMutex);
+
+    // Map between the divided vsync period and the last known vsync timestamp
+    std::unordered_map<nsecs_t, nsecs_t> mutable mRateDividerKnownTimestampMap GUARDED_BY(mMutex);
 
     size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 107c540..2cd9b3d 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -68,6 +68,14 @@
 
     virtual bool needsMoreSamples() const = 0;
 
+    /*
+     * Checks if a vsync timestamp is in phase for a given divider.
+     *
+     * \param [in] timePoint  A vsync timestamp
+     * \param [in] divider  The divider to check for
+     */
+    virtual bool isVSyncInPhase(nsecs_t timePoint, int divider) const = 0;
+
     virtual void dump(std::string& result) const = 0;
 
 protected: