Implement native PerformanceHint API

Test: atest PerformanceHintNativeTestCases
Bug: 194204196
Change-Id: Ie26e25e9ecf87046df92346dff54174934a8c73e
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 3ee2c18..32b7a07 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -57,6 +57,7 @@
         "net.c",
         "obb.cpp",
         "permission_manager.cpp",
+        "performance_hint.cpp",
         "sensor.cpp",
         "sharedmem.cpp",
         "storage_manager.cpp",
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index de6db1a..f33e118 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -312,6 +312,13 @@
 
 LIBANDROID_PLATFORM {
   global:
+    APerformanceHint_getManager;
+    APerformanceHint_createSession;
+    APerformanceHint_getPreferredUpdateRateNanos;
+    APerformanceHint_updateTargetWorkDuration;
+    APerformanceHint_reportActualWorkDuration;
+    APerformanceHint_closeSession;
+    APerformanceHint_setIHintManagerForTesting;
     extern "C++" {
         ASurfaceControl_registerSurfaceStatsListener*;
         ASurfaceControl_unregisterSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
new file mode 100644
index 0000000..95a2da9
--- /dev/null
+++ b/native/android/performance_hint.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "perf_hint"
+
+#include <utility>
+#include <vector>
+
+#include <android/os/IHintManager.h>
+#include <android/os/IHintSession.h>
+#include <binder/Binder.h>
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+#include <performance_hint_private.h>
+#include <utils/SystemClock.h>
+
+using namespace android;
+using namespace android::os;
+
+struct APerformanceHintSession;
+
+struct APerformanceHintManager {
+public:
+    static APerformanceHintManager* getInstance();
+    APerformanceHintManager(sp<IHintManager> service, int64_t preferredRateNanos);
+    APerformanceHintManager() = delete;
+    ~APerformanceHintManager() = default;
+
+    APerformanceHintSession* createSession(const int32_t* threadIds, size_t size,
+                                           int64_t initialTargetWorkDurationNanos);
+    int64_t getPreferredRateNanos() const;
+
+private:
+    static APerformanceHintManager* create(sp<IHintManager> iHintManager);
+
+    sp<IHintManager> mHintManager;
+    const int64_t mPreferredRateNanos;
+};
+
+struct APerformanceHintSession {
+public:
+    APerformanceHintSession(sp<IHintSession> session, int64_t preferredRateNanos,
+                            int64_t targetDurationNanos);
+    APerformanceHintSession() = delete;
+    ~APerformanceHintSession();
+
+    int updateTargetWorkDuration(int64_t targetDurationNanos);
+    int reportActualWorkDuration(int64_t actualDurationNanos);
+
+private:
+    friend struct APerformanceHintManager;
+
+    sp<IHintSession> mHintSession;
+    // HAL preferred update rate
+    const int64_t mPreferredRateNanos;
+    // Target duration for choosing update rate
+    int64_t mTargetDurationNanos;
+    // Last update timestamp
+    int64_t mLastUpdateTimestamp;
+    // Cached samples
+    std::vector<int64_t> mActualDurationsNanos;
+    std::vector<int64_t> mTimestampsNanos;
+};
+
+static IHintManager* gIHintManagerForTesting = nullptr;
+static APerformanceHintManager* gHintManagerForTesting = nullptr;
+
+// ===================================== APerformanceHintManager implementation
+APerformanceHintManager::APerformanceHintManager(sp<IHintManager> manager,
+                                                 int64_t preferredRateNanos)
+      : mHintManager(std::move(manager)), mPreferredRateNanos(preferredRateNanos) {}
+
+APerformanceHintManager* APerformanceHintManager::getInstance() {
+    if (gHintManagerForTesting) return gHintManagerForTesting;
+    if (gIHintManagerForTesting) {
+        APerformanceHintManager* manager = create(gIHintManagerForTesting);
+        gIHintManagerForTesting = nullptr;
+        return manager;
+    }
+    static APerformanceHintManager* instance = create(nullptr);
+    return instance;
+}
+
+APerformanceHintManager* APerformanceHintManager::create(sp<IHintManager> manager) {
+    if (!manager) {
+        manager = interface_cast<IHintManager>(
+                defaultServiceManager()->checkService(String16("performance_hint")));
+    }
+    if (manager == nullptr) {
+        ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__);
+        return nullptr;
+    }
+    int64_t preferredRateNanos = -1L;
+    binder::Status ret = manager->getHintSessionPreferredRate(&preferredRateNanos);
+    if (!ret.isOk()) {
+        ALOGE("%s: PerformanceHint cannot get preferred rate. %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        return nullptr;
+    }
+    if (preferredRateNanos <= 0) {
+        ALOGE("%s: PerformanceHint invalid preferred rate.", __FUNCTION__);
+        return nullptr;
+    }
+    return new APerformanceHintManager(std::move(manager), preferredRateNanos);
+}
+
+APerformanceHintSession* APerformanceHintManager::createSession(
+        const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos) {
+    sp<IBinder> token = sp<BBinder>::make();
+    std::vector<int32_t> tids(threadIds, threadIds + size);
+    sp<IHintSession> session;
+    binder::Status ret =
+            mHintManager->createHintSession(token, tids, initialTargetWorkDurationNanos, &session);
+    if (!ret.isOk() || !session) {
+        return nullptr;
+    }
+    return new APerformanceHintSession(std::move(session), mPreferredRateNanos,
+                                       initialTargetWorkDurationNanos);
+}
+
+int64_t APerformanceHintManager::getPreferredRateNanos() const {
+    return mPreferredRateNanos;
+}
+
+// ===================================== APerformanceHintSession implementation
+
+APerformanceHintSession::APerformanceHintSession(sp<IHintSession> session,
+                                                 int64_t preferredRateNanos,
+                                                 int64_t targetDurationNanos)
+      : mHintSession(std::move(session)),
+        mPreferredRateNanos(preferredRateNanos),
+        mTargetDurationNanos(targetDurationNanos),
+        mLastUpdateTimestamp(elapsedRealtimeNano()) {}
+
+APerformanceHintSession::~APerformanceHintSession() {
+    binder::Status ret = mHintSession->close();
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession close failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
+    }
+}
+
+int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNanos) {
+    if (targetDurationNanos <= 0) {
+        ALOGE("%s: targetDurationNanos must be positive", __FUNCTION__);
+        return EINVAL;
+    }
+    binder::Status ret = mHintSession->updateTargetWorkDuration(targetDurationNanos);
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSessionn updateTargetWorkDuration failed: %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        return EPIPE;
+    }
+    mTargetDurationNanos = targetDurationNanos;
+    /**
+     * Most of the workload is target_duration dependent, so now clear the cached samples
+     * as they are most likely obsolete.
+     */
+    mActualDurationsNanos.clear();
+    mTimestampsNanos.clear();
+    mLastUpdateTimestamp = elapsedRealtimeNano();
+    return 0;
+}
+
+int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) {
+    if (actualDurationNanos <= 0) {
+        ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
+        return EINVAL;
+    }
+    int64_t now = elapsedRealtimeNano();
+    mActualDurationsNanos.push_back(actualDurationNanos);
+    mTimestampsNanos.push_back(now);
+
+    /**
+     * Use current sample to determine the rate limit. We can pick a shorter rate limit
+     * if any sample underperformed, however, it could be the lower level system is slow
+     * to react. So here we explicitly choose the rate limit with the latest sample.
+     */
+    int64_t rateLimit = actualDurationNanos > mTargetDurationNanos ? mPreferredRateNanos
+                                                                   : 10 * mPreferredRateNanos;
+    if (now - mLastUpdateTimestamp <= rateLimit) return 0;
+
+    binder::Status ret =
+            mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
+    mActualDurationsNanos.clear();
+    mTimestampsNanos.clear();
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        return EPIPE;
+    }
+    mLastUpdateTimestamp = now;
+    return 0;
+}
+
+// ===================================== C API
+APerformanceHintManager* APerformanceHint_getManager() {
+    return APerformanceHintManager::getInstance();
+}
+
+APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* manager,
+                                                        const int32_t* threadIds, size_t size,
+                                                        int64_t initialTargetWorkDurationNanos) {
+    return manager->createSession(threadIds, size, initialTargetWorkDurationNanos);
+}
+
+int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager) {
+    return manager->getPreferredRateNanos();
+}
+
+int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session,
+                                              int64_t targetDurationNanos) {
+    return session->updateTargetWorkDuration(targetDurationNanos);
+}
+
+int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session,
+                                              int64_t actualDurationNanos) {
+    return session->reportActualWorkDuration(actualDurationNanos);
+}
+
+void APerformanceHint_closeSession(APerformanceHintSession* session) {
+    delete session;
+}
+
+void APerformanceHint_setIHintManagerForTesting(void* iManager) {
+    delete gHintManagerForTesting;
+    gHintManagerForTesting = nullptr;
+    gIHintManagerForTesting = static_cast<IHintManager*>(iManager);
+}
diff --git a/native/android/tests/performance_hint/Android.bp b/native/android/tests/performance_hint/Android.bp
new file mode 100644
index 0000000..fdc1bc6
--- /dev/null
+++ b/native/android/tests/performance_hint/Android.bp
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+cc_test {
+    name: "PerformanceHintNativeTestCases",
+
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    srcs: ["PerformanceHintNativeTest.cpp"],
+
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libbinder",
+        "libpowermanager",
+        "libutils",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libgmock",
+        "libgtest",
+    ],
+    stl: "c++_shared",
+
+    test_suites: [
+        "device-tests",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+    header_libs: [
+        "libandroid_headers_private",
+    ],
+}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
new file mode 100644
index 0000000..284e9ee
--- /dev/null
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "PerformanceHintNativeTest"
+
+#include <android/os/IHintManager.h>
+#include <android/os/IHintSession.h>
+#include <binder/IBinder.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <performance_hint_private.h>
+#include <memory>
+#include <vector>
+
+using android::binder::Status;
+using android::os::IHintManager;
+using android::os::IHintSession;
+
+using namespace android;
+using namespace testing;
+
+class MockIHintManager : public IHintManager {
+public:
+    MOCK_METHOD(Status, createHintSession,
+                (const ::android::sp<::android::IBinder>& token, const ::std::vector<int32_t>& tids,
+                 int64_t durationNanos, ::android::sp<::android::os::IHintSession>* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * _aidl_return), (override));
+    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+class MockIHintSession : public IHintSession {
+public:
+    MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t targetDurationNanos), (override));
+    MOCK_METHOD(Status, reportActualWorkDuration,
+                (const ::std::vector<int64_t>& actualDurationNanos,
+                 const ::std::vector<int64_t>& timeStampNanos),
+                (override));
+    MOCK_METHOD(Status, close, (), (override));
+    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+class PerformanceHintTest : public Test {
+public:
+    void SetUp() override {
+        mMockIHintManager = new StrictMock<MockIHintManager>();
+        APerformanceHint_setIHintManagerForTesting(mMockIHintManager);
+    }
+
+    void TearDown() override {
+        mMockIHintManager = nullptr;
+        // Destroys MockIHintManager.
+        APerformanceHint_setIHintManagerForTesting(nullptr);
+    }
+
+    APerformanceHintManager* createManager() {
+        EXPECT_CALL(*mMockIHintManager, getHintSessionPreferredRate(_))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<0>(123L), Return(Status())));
+        return APerformanceHint_getManager();
+    }
+
+    StrictMock<MockIHintManager>* mMockIHintManager = nullptr;
+};
+
+TEST_F(PerformanceHintTest, TestGetPreferredUpdateRateNanos) {
+    APerformanceHintManager* manager = createManager();
+    int64_t preferredUpdateRateNanos = APerformanceHint_getPreferredUpdateRateNanos(manager);
+    EXPECT_EQ(123L, preferredUpdateRateNanos);
+}
+
+TEST_F(PerformanceHintTest, TestSession) {
+    APerformanceHintManager* manager = createManager();
+
+    std::vector<int32_t> tids;
+    tids.push_back(1);
+    tids.push_back(2);
+    int64_t targetDuration = 56789L;
+
+    StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
+    sp<IHintSession> session_sp(iSession);
+
+    EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
+
+    APerformanceHintSession* session =
+            APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
+    ASSERT_TRUE(session);
+
+    int64_t targetDurationNanos = 10;
+    EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1));
+    int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos);
+    EXPECT_EQ(0, result);
+
+    usleep(2); // Sleep for longer than preferredUpdateRateNanos.
+    int64_t actualDurationNanos = 20;
+    std::vector<int64_t> actualDurations;
+    actualDurations.push_back(20);
+    EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1));
+    result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos);
+    EXPECT_EQ(0, result);
+
+    result = APerformanceHint_updateTargetWorkDuration(session, -1L);
+    EXPECT_EQ(EINVAL, result);
+    result = APerformanceHint_reportActualWorkDuration(session, -1L);
+    EXPECT_EQ(EINVAL, result);
+
+    EXPECT_CALL(*iSession, close()).Times(Exactly(1));
+    APerformanceHint_closeSession(session);
+}