Add rate limiter to sendHint API call

Adds a rate limiter to the HintSession.sendHint API for safety, and
update tests to reflect this.

Bug: b/243973548
Test: atest PerformanceHintNativeTestCases
Test: atest FrameworksCoreTests:android.os.PerformanceHintManagerTest
Change-Id: Ic68683bacf7df3e11efc3d59689b5470c3fa4274
diff --git a/native/android/Android.bp b/native/android/Android.bp
index f1b1d79..254eb44 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -95,6 +95,7 @@
         "libpowermanager",
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
+        "android.hardware.power-V4-ndk",
         "libnativedisplay",
     ],
 
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 40eb507..9e97bd3 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "perf_hint"
 
+#include <aidl/android/hardware/power/SessionHint.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
@@ -25,14 +26,21 @@
 #include <performance_hint_private.h>
 #include <utils/SystemClock.h>
 
+#include <chrono>
 #include <utility>
 #include <vector>
 
 using namespace android;
 using namespace android::os;
 
+using namespace std::chrono_literals;
+
+using AidlSessionHint = aidl::android::hardware::power::SessionHint;
+
 struct APerformanceHintSession;
 
+constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
+
 struct APerformanceHintManager {
 public:
     static APerformanceHintManager* getInstance();
@@ -75,6 +83,8 @@
     int64_t mFirstTargetMetTimestamp;
     // Last target hit timestamp
     int64_t mLastTargetMetTimestamp;
+    // Last hint reported from sendHint indexed by hint value
+    std::vector<int64_t> mLastHintSentTimestamp;
     // Cached samples
     std::vector<int64_t> mActualDurationsNanos;
     std::vector<int64_t> mTimestampsNanos;
@@ -147,7 +157,12 @@
         mPreferredRateNanos(preferredRateNanos),
         mTargetDurationNanos(targetDurationNanos),
         mFirstTargetMetTimestamp(0),
-        mLastTargetMetTimestamp(0) {}
+        mLastTargetMetTimestamp(0) {
+    const std::vector<AidlSessionHint> sessionHintRange{ndk::enum_range<AidlSessionHint>().begin(),
+                                                        ndk::enum_range<AidlSessionHint>().end()};
+
+    mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
+}
 
 APerformanceHintSession::~APerformanceHintSession() {
     binder::Status ret = mHintSession->close();
@@ -224,10 +239,16 @@
 }
 
 int APerformanceHintSession::sendHint(int32_t hint) {
-    if (hint < 0) {
-        ALOGE("%s: session hint value must be greater than zero", __FUNCTION__);
+    if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) {
+        ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
         return EINVAL;
     }
+    int64_t now = elapsedRealtimeNano();
+
+    // Limit sendHint to a pre-detemined rate for safety
+    if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) {
+        return 0;
+    }
 
     binder::Status ret = mHintSession->sendHint(hint);
 
@@ -235,6 +256,7 @@
         ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
         return EPIPE;
     }
+    mLastHintSentTimestamp[hint] = now;
     return 0;
 }
 
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 1881e60..0c2d3b6 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -122,9 +122,16 @@
     result = APerformanceHint_reportActualWorkDuration(session, -1L);
     EXPECT_EQ(EINVAL, result);
 
-    // Send both valid and invalid session hints
     int hintId = 2;
-    EXPECT_CALL(*iSession, sendHint(Eq(2))).Times(Exactly(1));
+    EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_EQ(0, result);
+    usleep(110000); // Sleep for longer than the update timeout.
+    EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_EQ(0, result);
+    // Expect to get rate limited if we try to send faster than the limiter allows
+    EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(0));
     result = APerformanceHint_sendHint(session, hintId);
     EXPECT_EQ(0, result);