Add support for ADPF HintSession sendHint to JNI

 * Add sendHint API to HintManagerService and PerformanceHintManager
 * Plumb relevant calls through the existing implementation
 * Extend existing tests to cover new API calls
 * Update the relevant build files to use power API v4

Bug: b/243973548
Test: atest PerformanceHintNativeTestCases
Test: atest FrameworksCoreTests:android.os.PerformanceHintManagerTest
Test: atest HintManagerServiceTest

Change-Id: Ice7ed8f32e877bd845afad77fcc6ae16f1a1b78c
diff --git a/core/api/current.txt b/core/api/current.txt
index c824106..e28006a0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32165,7 +32165,12 @@
   public static class PerformanceHintManager.Session implements java.io.Closeable {
     method public void close();
     method public void reportActualWorkDuration(long);
+    method public void sendHint(int);
     method public void updateTargetWorkDuration(long);
+    field public static final int CPU_LOAD_DOWN = 1; // 0x1
+    field public static final int CPU_LOAD_RESET = 2; // 0x2
+    field public static final int CPU_LOAD_RESUME = 3; // 0x3
+    field public static final int CPU_LOAD_UP = 0; // 0x0
   }
 
   public final class PersistableBundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 09bc4cc..0d1dde1 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -22,4 +22,5 @@
     void updateTargetWorkDuration(long targetDurationNanos);
     void reportActualWorkDuration(in long[] actualDurationNanos, in long[] timeStampNanos);
     void close();
+    void sendHint(int hint);
 }
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index a75b5ef..86135bc 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
@@ -24,6 +25,10 @@
 import com.android.internal.util.Preconditions;
 
 import java.io.Closeable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
+
 
 /** The PerformanceHintManager allows apps to send performance hint to system. */
 @SystemService(Context.PERFORMANCE_HINT_SERVICE)
@@ -104,6 +109,40 @@
             mNativeSessionPtr = nativeSessionPtr;
         }
 
+        /**
+        * This hint indicates a sudden increase in CPU workload intensity. It means
+        * that this hint session needs extra CPU resources immediately to meet the
+        * target duration for the current work cycle.
+        */
+        public static final int CPU_LOAD_UP = 0;
+        /**
+        * This hint indicates a decrease in CPU workload intensity. It means that
+        * this hint session can reduce CPU resources and still meet the target duration.
+        */
+        public static final int CPU_LOAD_DOWN = 1;
+        /*
+        * This hint indicates an upcoming CPU workload that is completely changed and
+        * unknown. It means that the hint session should reset CPU resources to a known
+        * baseline to prepare for an arbitrary load, and must wake up if inactive.
+        */
+        public static final int CPU_LOAD_RESET = 2;
+        /*
+        * This hint indicates that the most recent CPU workload is resuming after a
+        * period of inactivity. It means that the hint session should allocate similar
+        * CPU resources to what was used previously, and must wake up if inactive.
+        */
+        public static final int CPU_LOAD_RESUME = 3;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"CPU_LOAD_"}, value = {
+            CPU_LOAD_UP,
+            CPU_LOAD_DOWN,
+            CPU_LOAD_RESET,
+            CPU_LOAD_RESUME
+        })
+        public @interface Hint {}
+
         /** @hide */
         @Override
         protected void finalize() throws Throwable {
@@ -152,6 +191,21 @@
                 mNativeSessionPtr = 0;
             }
         }
+
+        /**
+         * Sends performance hints to inform the hint session of changes in the workload.
+         *
+         * @param hint The hint to send to the session.
+         */
+        public void sendHint(@Hint int hint) {
+            Preconditions.checkArgumentNonNegative(hint, "the hint ID should be at least"
+                    + " zero.");
+            try {
+                nativeSendHint(mNativeSessionPtr, hint);
+            } finally {
+                Reference.reachabilityFence(this);
+            }
+        }
     }
 
     private static native long nativeAcquireManager();
@@ -163,4 +217,5 @@
     private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
             long actualDurationNanos);
     private static native void nativeCloseSession(long nativeSessionPtr);
+    private static native void nativeSendHint(long nativeSessionPtr, int hint);
 }
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index d05a24f..ac1401d 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -40,6 +40,7 @@
 typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
 typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
 typedef void (*APH_closeSession)(APerformanceHintSession* session);
+typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t);
 
 bool gAPerformanceHintBindingInitialized = false;
 APH_getManager gAPH_getManagerFn = nullptr;
@@ -48,6 +49,7 @@
 APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
 APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
 APH_closeSession gAPH_closeSessionFn = nullptr;
+APH_sendHint gAPH_sendHintFn = nullptr;
 
 void ensureAPerformanceHintBindingInitialized() {
     if (gAPerformanceHintBindingInitialized) return;
@@ -88,6 +90,11 @@
     LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
                         "Failed to find required symbol APerformanceHint_closeSession!");
 
+    gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
+    LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
+                        "Failed to find required symbol "
+                        "APerformanceHint_sendHint!");
+
     gAPerformanceHintBindingInitialized = true;
 }
 
@@ -138,6 +145,11 @@
     gAPH_closeSessionFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr));
 }
 
+static void nativeSendHint(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, jint hint) {
+    ensureAPerformanceHintBindingInitialized();
+    gAPH_sendHintFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), hint);
+}
+
 static const JNINativeMethod gPerformanceHintMethods[] = {
         {"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
         {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -145,6 +157,7 @@
         {"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
         {"nativeReportActualWorkDuration", "(JJ)V", (void*)nativeReportActualWorkDuration},
         {"nativeCloseSession", "(J)V", (void*)nativeCloseSession},
+        {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
 };
 
 int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 69eb13f..d1d14f6 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -114,6 +114,23 @@
     }
 
     @Test
+    public void testSendHint() {
+        Session s = createSession();
+        assumeNotNull(s);
+        s.sendHint(Session.CPU_LOAD_UP);
+        s.sendHint(Session.CPU_LOAD_RESET);
+    }
+
+    @Test
+    public void testSendHintWithNegativeHint() {
+        Session s = createSession();
+        assumeNotNull(s);
+        assertThrows(IllegalArgumentException.class, () -> {
+            s.sendHint(-1);
+        });
+    }
+
+    @Test
     public void testCloseHintSession() {
         Session s = createSession();
         assumeNotNull(s);
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index cb0f22f..584d0ba2 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -330,6 +330,7 @@
     APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
     APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
     APerformanceHint_closeSession; # introduced=Tiramisu
+    APerformanceHint_sendHint; # introduced=UpsideDownCake
   local:
     *;
 };
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index d627984..7863a7d 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -61,6 +61,7 @@
 
     int updateTargetWorkDuration(int64_t targetDurationNanos);
     int reportActualWorkDuration(int64_t actualDurationNanos);
+    int sendHint(int32_t hint);
 
 private:
     friend struct APerformanceHintManager;
@@ -159,7 +160,7 @@
     }
     binder::Status ret = mHintSession->updateTargetWorkDuration(targetDurationNanos);
     if (!ret.isOk()) {
-        ALOGE("%s: HintSessionn updateTargetWorkDuration failed: %s", __FUNCTION__,
+        ALOGE("%s: HintSession updateTargetWorkDuration failed: %s", __FUNCTION__,
               ret.exceptionMessage().c_str());
         return EPIPE;
     }
@@ -205,6 +206,21 @@
     return 0;
 }
 
+int APerformanceHintSession::sendHint(int32_t hint) {
+    if (hint < 0) {
+        ALOGE("%s: session hint value must be greater than zero", __FUNCTION__);
+        return EINVAL;
+    }
+
+    binder::Status ret = mHintSession->sendHint(hint);
+
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
+        return EPIPE;
+    }
+    return 0;
+}
+
 // ===================================== C API
 APerformanceHintManager* APerformanceHint_getManager() {
     return APerformanceHintManager::getInstance();
@@ -230,6 +246,10 @@
     return session->reportActualWorkDuration(actualDurationNanos);
 }
 
+int APerformanceHint_sendHint(APerformanceHintSession* session, int32_t hint) {
+    return session->sendHint(hint);
+}
+
 void APerformanceHint_closeSession(APerformanceHintSession* session) {
     delete session;
 }
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index b17850e..1881e60 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -51,6 +51,7 @@
                 (const ::std::vector<int64_t>& actualDurationNanos,
                  const ::std::vector<int64_t>& timeStampNanos),
                 (override));
+    MOCK_METHOD(Status, sendHint, (int32_t hints), (override));
     MOCK_METHOD(Status, close, (), (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
 };
@@ -121,6 +122,15 @@
     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));
+    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_EQ(0, result);
+
+    result = APerformanceHint_sendHint(session, -1);
+    EXPECT_EQ(EINVAL, result);
+
     EXPECT_CALL(*iSession, close()).Times(Exactly(1));
     APerformanceHint_closeSession(session);
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 553146d..84f2b63 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -168,7 +168,7 @@
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.soundtrigger-V2.3-java",
         "android.hardware.power.stats-V1-java",
-        "android.hardware.power-V3-java",
+        "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
         "capture_state_listener-aidl-java",
         "icu4j_calendar_astronomer",
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index dfa1281..0d13831 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.os.IHintManager;
 import android.os.IHintSession;
+import android.os.PerformanceHintManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
@@ -147,6 +148,8 @@
         private static native void nativeReportActualWorkDuration(
                 long halPtr, long[] actualDurationNanos, long[] timeStampNanos);
 
+        private static native void nativeSendHint(long halPtr, int hint);
+
         private static native long nativeGetHintSessionPreferredRate();
 
         /** Wrapper for HintManager.nativeInit */
@@ -186,6 +189,11 @@
                     timeStampNanos);
         }
 
+        /** Wrapper for HintManager.sendHint */
+        public void halSendHint(long halPtr, int hint) {
+            nativeSendHint(halPtr, hint);
+        }
+
         /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
         public long halGetHintSessionPreferredRate() {
             return nativeGetHintSessionPreferredRate();
@@ -475,6 +483,18 @@
             }
         }
 
+        @Override
+        public void sendHint(@PerformanceHintManager.Session.Hint int hint) {
+            synchronized (mLock) {
+                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+                    return;
+                }
+                Preconditions.checkArgument(hint >= 0, "the hint ID the hint value should be"
+                        + " greater than zero.");
+                mNativeWrapper.halSendHint(mHalSessionPtr, hint);
+            }
+        }
+
         private void onProcStateChanged() {
             updateHintAllowed();
         }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 3c78819..57b977c 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -170,7 +170,7 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V3-cpp",
+        "android.hardware.power-V4-cpp",
         "android.hardware.power.stats@1.0",
         "android.hardware.power.stats-V1-ndk",
         "android.hardware.thermal@1.0",
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index 000cb83..d975760 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -34,6 +34,7 @@
 #include "jni.h"
 
 using android::hardware::power::IPowerHintSession;
+using android::hardware::power::SessionHint;
 using android::hardware::power::WorkDuration;
 
 using android::base::StringPrintf;
@@ -81,6 +82,11 @@
     appSession->reportActualWorkDuration(actualDurations);
 }
 
+static void sendHint(int64_t session_ptr, SessionHint hint) {
+    sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    appSession->sendHint(hint);
+}
+
 static int64_t getHintSessionPreferredRate() {
     int64_t rate = -1;
     auto result = gPowerHalController.getHintSessionPreferredRate();
@@ -139,6 +145,10 @@
     reportActualWorkDuration(session_ptr, actualList);
 }
 
+static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint hint) {
+    sendHint(session_ptr, static_cast<SessionHint>(hint));
+}
+
 static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
     return static_cast<jlong>(getHintSessionPreferredRate());
 }
@@ -153,6 +163,7 @@
         {"nativeCloseHintSession", "(J)V", (void*)nativeCloseHintSession},
         {"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
         {"nativeReportActualWorkDuration", "(J[J[J)V", (void*)nativeReportActualWorkDuration},
+        {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
         {"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
 };
 
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 397770b..dcbdcdc 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -42,6 +42,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.IHintSession;
+import android.os.PerformanceHintManager;
 import android.os.Process;
 
 import com.android.server.FgThread;
@@ -250,6 +251,32 @@
     }
 
     @Test
+    public void testSendHint() throws Exception {
+        HintManagerService service = createService();
+        IBinder token = new Binder();
+
+        AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+                .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+        a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
+        verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(),
+                eq(PerformanceHintManager.Session.CPU_LOAD_RESET));
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            a.sendHint(-1);
+        });
+
+        reset(mNativeWrapperMock);
+        // Set session to background, then the duration would not be updated.
+        service.mUidObserver.onUidStateChanged(
+                a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        FgThread.getHandler().runWithScissors(() -> { }, 500);
+        assertFalse(a.updateHintAllowed());
+        a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
+        verify(mNativeWrapperMock, never()).halSendHint(anyLong(), anyInt());
+    }
+
+    @Test
     public void testDoHintInBackground() throws Exception {
         HintManagerService service = createService();
         IBinder token = new Binder();