Build a JVM-compatible version of libnativehelper

libnativehelper is used for some common methods involving communication
between the Java and native parts of Android. Some of those methods are
specialized for ART and do not work on a JVM (due to some API
differences between ART and JVM).
libnativehelper_jvm implements the same methods in a JVM-compatible way,
so that it can be used whenever a library is meant to be run on a host
JVM.

Bug: 322360037
Test: build libnativehelper_jvm on host
Change-Id: I6aa72b3f69fe2df946febcc5eaa6e734448c70af
diff --git a/libs/nativehelper_jvm/Android.bp b/libs/nativehelper_jvm/Android.bp
new file mode 100644
index 0000000..b5b7028
--- /dev/null
+++ b/libs/nativehelper_jvm/Android.bp
@@ -0,0 +1,19 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+cc_library_host_static {
+    name: "libnativehelper_jvm",
+    srcs: [
+        "JNIPlatformHelp.c",
+        "JniConstants.c",
+        "file_descriptor_jni.c",
+    ],
+    whole_static_libs: ["libnativehelper_any_vm"],
+    export_static_lib_headers: ["libnativehelper_any_vm"],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
+}
diff --git a/libs/nativehelper_jvm/JNIPlatformHelp.c b/libs/nativehelper_jvm/JNIPlatformHelp.c
new file mode 100644
index 0000000..9df31a8
--- /dev/null
+++ b/libs/nativehelper_jvm/JNIPlatformHelp.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <nativehelper/JNIPlatformHelp.h>
+
+#include <stddef.h>
+
+#include "JniConstants.h"
+
+static int GetBufferPosition(JNIEnv* env, jobject nioBuffer) {
+    return(*env)->GetIntField(env, nioBuffer, JniConstants_NioBuffer_position(env));
+}
+
+static int GetBufferLimit(JNIEnv* env, jobject nioBuffer) {
+    return(*env)->GetIntField(env, nioBuffer, JniConstants_NioBuffer_limit(env));
+}
+
+static int GetBufferElementSizeShift(JNIEnv* env, jobject nioBuffer) {
+    jclass byteBufferClass = JniConstants_NioByteBufferClass(env);
+    jclass shortBufferClass = JniConstants_NioShortBufferClass(env);
+    jclass charBufferClass = JniConstants_NioCharBufferClass(env);
+    jclass intBufferClass = JniConstants_NioIntBufferClass(env);
+    jclass floatBufferClass = JniConstants_NioFloatBufferClass(env);
+    jclass longBufferClass = JniConstants_NioLongBufferClass(env);
+    jclass doubleBufferClass = JniConstants_NioDoubleBufferClass(env);
+
+    // Check the type of the Buffer
+    if ((*env)->IsInstanceOf(env, nioBuffer, byteBufferClass)) {
+        return 0;
+    } else if ((*env)->IsInstanceOf(env, nioBuffer, shortBufferClass) ||
+               (*env)->IsInstanceOf(env, nioBuffer, charBufferClass)) {
+        return 1;
+    } else if ((*env)->IsInstanceOf(env, nioBuffer, intBufferClass) ||
+               (*env)->IsInstanceOf(env, nioBuffer, floatBufferClass)) {
+        return 2;
+    } else if ((*env)->IsInstanceOf(env, nioBuffer, longBufferClass) ||
+               (*env)->IsInstanceOf(env, nioBuffer, doubleBufferClass)) {
+        return 3;
+    }
+    return 0;
+}
+
+jarray jniGetNioBufferBaseArray(JNIEnv* env, jobject nioBuffer) {
+    jmethodID hasArrayMethod = JniConstants_NioBuffer_hasArray(env);
+    jboolean hasArray = (*env)->CallBooleanMethod(env, nioBuffer, hasArrayMethod);
+    if (hasArray) {
+        jmethodID arrayMethod = JniConstants_NioBuffer_array(env);
+        return (*env)->CallObjectMethod(env, nioBuffer, arrayMethod);
+    } else {
+        return NULL;
+    }
+}
+
+int jniGetNioBufferBaseArrayOffset(JNIEnv* env, jobject nioBuffer) {
+    jmethodID hasArrayMethod = JniConstants_NioBuffer_hasArray(env);
+    jboolean hasArray = (*env)->CallBooleanMethod(env, nioBuffer, hasArrayMethod);
+    if (hasArray) {
+        jmethodID arrayOffsetMethod = JniConstants_NioBuffer_arrayOffset(env);
+        jint arrayOffset = (*env)->CallIntMethod(env, nioBuffer, arrayOffsetMethod);
+        const int position = GetBufferPosition(env, nioBuffer);
+        jint elementSizeShift = GetBufferElementSizeShift(env, nioBuffer);
+        return (arrayOffset + position) << elementSizeShift;
+    } else {
+        return 0;
+    }
+}
+
+jlong jniGetNioBufferPointer(JNIEnv* env, jobject nioBuffer) {
+    // in Java 11, the address field of a HeapByteBuffer contains a non-zero value despite
+    // HeapByteBuffer being a non-direct buffer. In that case, this should still return 0.
+    jmethodID isDirectMethod = JniConstants_NioBuffer_isDirect(env);
+    jboolean isDirect = (*env)->CallBooleanMethod(env, nioBuffer, isDirectMethod);
+    if (isDirect == JNI_FALSE) {
+        return 0L;
+    }
+    jlong baseAddress = (*env)->GetLongField(env, nioBuffer, JniConstants_NioBuffer_address(env));
+    if (baseAddress != 0) {
+        const int position = GetBufferPosition(env, nioBuffer);
+        const int shift = GetBufferElementSizeShift(env, nioBuffer);
+        baseAddress += position << shift;
+    }
+    return baseAddress;
+}
+
+jlong jniGetNioBufferFields(JNIEnv* env, jobject nioBuffer,
+                            jint* position, jint* limit, jint* elementSizeShift) {
+    *position = GetBufferPosition(env, nioBuffer);
+    *limit = GetBufferLimit(env, nioBuffer);
+    *elementSizeShift = GetBufferElementSizeShift(env, nioBuffer);
+    return (*env)->GetLongField(env, nioBuffer, JniConstants_NioBuffer_address(env));
+}
diff --git a/libs/nativehelper_jvm/JniConstants.c b/libs/nativehelper_jvm/JniConstants.c
new file mode 100644
index 0000000..ca58f61
--- /dev/null
+++ b/libs/nativehelper_jvm/JniConstants.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include "JniConstants.h"
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+#define LOG_TAG "JniConstants"
+#include <log/log.h>
+
+// jclass constants list:
+//   <class, signature, androidOnly>
+#define JCLASS_CONSTANTS_LIST(V)                                            \
+  V(FileDescriptor, "java/io/FileDescriptor", false)                        \
+  V(NioBuffer, "java/nio/Buffer", false)                                    \
+  V(NioByteBuffer, "java/nio/ByteBuffer", false)                            \
+  V(NioShortBuffer, "java/nio/ShortBuffer", false)                          \
+  V(NioCharBuffer, "java/nio/CharBuffer", false)                            \
+  V(NioIntBuffer, "java/nio/IntBuffer", false)                              \
+  V(NioFloatBuffer, "java/nio/FloatBuffer", false)                          \
+  V(NioLongBuffer, "java/nio/LongBuffer", false)                            \
+  V(NioDoubleBuffer, "java/nio/DoubleBuffer", false)
+
+// jmethodID's of public methods constants list:
+//   <Class, method, method-string, signature, is_static>
+#define JMETHODID_CONSTANTS_LIST(V)                                                         \
+  V(FileDescriptor, init, "<init>", "()V", false)                                           \
+  V(NioBuffer, array, "array", "()Ljava/lang/Object;", false)                               \
+  V(NioBuffer, hasArray, "hasArray", "()Z", false)                                          \
+  V(NioBuffer, isDirect, "isDirect", "()Z", false)                                          \
+  V(NioBuffer, arrayOffset, "arrayOffset", "()I", false)
+
+// jfieldID constants list:
+//   <Class, field, signature, is_static>
+#define JFIELDID_CONSTANTS_LIST(V)                                          \
+  V(FileDescriptor, fd, "I", false)                                         \
+  V(NioBuffer, address, "J", false)                                         \
+  V(NioBuffer, limit, "I", false)                                           \
+  V(NioBuffer, position, "I", false)
+
+#define CLASS_NAME(cls)             g_ ## cls
+#define METHOD_NAME(cls, method)    g_ ## cls ## _ ## method
+#define FIELD_NAME(cls, field)      g_ ## cls ## _ ## field
+
+//
+// Declare storage for cached classes, methods and fields.
+//
+
+#define JCLASS_DECLARE_STORAGE(cls, ...)                                    \
+  static jclass CLASS_NAME(cls) = NULL;
+JCLASS_CONSTANTS_LIST(JCLASS_DECLARE_STORAGE)
+#undef JCLASS_DECLARE_STORAGE
+
+#define JMETHODID_DECLARE_STORAGE(cls, method, ...)                         \
+  static jmethodID METHOD_NAME(cls, method) = NULL;
+JMETHODID_CONSTANTS_LIST(JMETHODID_DECLARE_STORAGE)
+#undef JMETHODID_DECLARE_STORAGE
+
+#define JFIELDID_DECLARE_STORAGE(cls, field, ...)                           \
+  static jfieldID FIELD_NAME(cls, field) = NULL;
+JFIELDID_CONSTANTS_LIST(JFIELDID_DECLARE_STORAGE)
+#undef JFIELDID_DECLARE_STORAGE
+
+//
+// Helper methods
+//
+
+static jclass FindClass(JNIEnv* env, const char* signature, bool androidOnly) {
+    jclass cls = (*env)->FindClass(env, signature);
+    if (cls == NULL) {
+        LOG_ALWAYS_FATAL_IF(!androidOnly, "Class not found: %s", signature);
+        return NULL;
+    }
+    return (*env)->NewGlobalRef(env, cls);
+}
+
+static jmethodID FindMethod(JNIEnv* env, jclass cls,
+                            const char* name, const char* signature, bool isStatic) {
+    jmethodID method;
+    if (isStatic) {
+        method = (*env)->GetStaticMethodID(env, cls, name, signature);
+    } else {
+        method = (*env)->GetMethodID(env, cls, name, signature);
+    }
+    LOG_ALWAYS_FATAL_IF(method == NULL, "Method not found: %s:%s", name, signature);
+    return method;
+}
+
+static jfieldID FindField(JNIEnv* env, jclass cls,
+                          const char* name, const char* signature, bool isStatic) {
+    jfieldID field;
+    if (isStatic) {
+        field = (*env)->GetStaticFieldID(env, cls, name, signature);
+    } else {
+        field = (*env)->GetFieldID(env, cls, name, signature);
+    }
+    LOG_ALWAYS_FATAL_IF(field == NULL, "Field not found: %s:%s", name, signature);
+    return field;
+}
+
+static pthread_once_t g_initialized = PTHREAD_ONCE_INIT;
+static JNIEnv* g_init_env;
+
+static void InitializeConstants() {
+    // Initialize cached classes.
+#define JCLASS_INITIALIZE(cls, signature, androidOnly)                      \
+    CLASS_NAME(cls) = FindClass(g_init_env, signature, androidOnly);
+    JCLASS_CONSTANTS_LIST(JCLASS_INITIALIZE)
+#undef JCLASS_INITIALIZE
+
+    // Initialize cached methods.
+#define JMETHODID_INITIALIZE(cls, method, name, signature, isStatic)        \
+    METHOD_NAME(cls, method) =                                              \
+        FindMethod(g_init_env, CLASS_NAME(cls), name, signature, isStatic);
+    JMETHODID_CONSTANTS_LIST(JMETHODID_INITIALIZE)
+#undef JMETHODID_INITIALIZE
+
+    // Initialize cached fields.
+#define JFIELDID_INITIALIZE(cls, field, signature, isStatic)                \
+    FIELD_NAME(cls, field) =                                                \
+        FindField(g_init_env, CLASS_NAME(cls), #field, signature, isStatic);
+    JFIELDID_CONSTANTS_LIST(JFIELDID_INITIALIZE)
+#undef JFIELDID_INITIALIZE
+}
+
+void EnsureInitialized(JNIEnv* env) {
+    // This method has to be called in every cache accesses because library can be built
+    // 2 different ways and existing usage for compat version doesn't have a good hook for
+    // initialization and is widely used.
+    g_init_env = env;
+    pthread_once(&g_initialized, InitializeConstants);
+}
+
+// API exported by libnativehelper_api.h.
+
+void jniUninitializeConstants() {
+    // Uninitialize cached classes, methods and fields.
+    //
+    // NB we assume the runtime is stopped at this point and do not delete global
+    // references.
+#define JCLASS_INVALIDATE(cls, ...) CLASS_NAME(cls) = NULL;
+    JCLASS_CONSTANTS_LIST(JCLASS_INVALIDATE);
+#undef JCLASS_INVALIDATE
+
+#define JMETHODID_INVALIDATE(cls, method, ...) METHOD_NAME(cls, method) = NULL;
+    JMETHODID_CONSTANTS_LIST(JMETHODID_INVALIDATE);
+#undef JMETHODID_INVALIDATE
+
+#define JFIELDID_INVALIDATE(cls, field, ...) FIELD_NAME(cls, field) = NULL;
+    JFIELDID_CONSTANTS_LIST(JFIELDID_INVALIDATE);
+#undef JFIELDID_INVALIDATE
+
+    // If jniConstantsUninitialize is called, runtime has shutdown. Reset
+    // state as some tests re-start the runtime.
+    pthread_once_t o = PTHREAD_ONCE_INIT;
+    memcpy(&g_initialized, &o, sizeof(o));
+}
+
+//
+// Accessors
+//
+
+#define JCLASS_ACCESSOR_IMPL(cls, ...)                                      \
+jclass JniConstants_ ## cls ## Class(JNIEnv* env) {                         \
+    EnsureInitialized(env);                                                 \
+    return CLASS_NAME(cls);                                                 \
+}
+JCLASS_CONSTANTS_LIST(JCLASS_ACCESSOR_IMPL)
+#undef JCLASS_ACCESSOR_IMPL
+
+#define JMETHODID_ACCESSOR_IMPL(cls, method, ...)                           \
+jmethodID JniConstants_ ## cls ## _ ## method(JNIEnv* env) {                \
+    EnsureInitialized(env);                                                 \
+    return METHOD_NAME(cls, method);                                        \
+}
+JMETHODID_CONSTANTS_LIST(JMETHODID_ACCESSOR_IMPL)
+
+#define JFIELDID_ACCESSOR_IMPL(cls, field, ...)                             \
+jfieldID JniConstants_ ## cls ## _ ## field(JNIEnv* env) {                  \
+    EnsureInitialized(env);                                                 \
+    return FIELD_NAME(cls, field);                                          \
+}
+JFIELDID_CONSTANTS_LIST(JFIELDID_ACCESSOR_IMPL)
diff --git a/libs/nativehelper_jvm/JniConstants.h b/libs/nativehelper_jvm/JniConstants.h
new file mode 100644
index 0000000..e7a266d
--- /dev/null
+++ b/libs/nativehelper_jvm/JniConstants.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+#include <jni.h>
+
+__BEGIN_DECLS
+
+//
+// Classes in constants cache.
+//
+// NB The implementations of these methods are generated by the JCLASS_ACCESSOR_IMPL macro in
+// JniConstants.c.
+//
+jclass JniConstants_FileDescriptorClass(JNIEnv* env);
+jclass JniConstants_NioByteBufferClass(JNIEnv* env);
+jclass JniConstants_NioShortBufferClass(JNIEnv* env);
+jclass JniConstants_NioCharBufferClass(JNIEnv* env);
+jclass JniConstants_NioIntBufferClass(JNIEnv* env);
+jclass JniConstants_NioFloatBufferClass(JNIEnv* env);
+jclass JniConstants_NioLongBufferClass(JNIEnv* env);
+jclass JniConstants_NioDoubleBufferClass(JNIEnv* env);
+
+//
+// Methods in the constants cache.
+//
+// NB The implementations of these methods are generated by the JMETHODID_ACCESSOR_IMPL macro in
+// JniConstants.c.
+//
+jmethodID JniConstants_FileDescriptor_init(JNIEnv* env);
+jmethodID JniConstants_NioBuffer_array(JNIEnv* env);
+jmethodID JniConstants_NioBuffer_arrayOffset(JNIEnv* env);
+jmethodID JniConstants_NioBuffer_hasArray(JNIEnv* env);
+jmethodID JniConstants_NioBuffer_isDirect(JNIEnv* env);
+
+//
+// Fields in the constants cache.
+//
+// NB The implementations of these methods are generated by the JFIELDID_ACCESSOR_IMPL macro in
+// JniConstants.c.
+//
+jfieldID JniConstants_FileDescriptor_fd(JNIEnv* env);
+jfieldID JniConstants_NioBuffer_address(JNIEnv* env);
+jfieldID JniConstants_NioBuffer_limit(JNIEnv* env);
+jfieldID JniConstants_NioBuffer_position(JNIEnv* env);
+
+__END_DECLS
diff --git a/libs/nativehelper_jvm/OWNERS b/libs/nativehelper_jvm/OWNERS
new file mode 100644
index 0000000..5d55f6e
--- /dev/null
+++ b/libs/nativehelper_jvm/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 326772
+
+include /libs/hwui/OWNERS
+include platform/libnativehelper:/OWNERS
+
+diegoperez@google.com
+jgaillard@google.com
diff --git a/libs/nativehelper_jvm/README b/libs/nativehelper_jvm/README
new file mode 100644
index 0000000..755c422
--- /dev/null
+++ b/libs/nativehelper_jvm/README
@@ -0,0 +1,2 @@
+libnativehelper_jvm is a JVM-compatible version of libnativehelper.
+It should be used instead of libnativehelper whenever a host library is meant to run on a JVM.
\ No newline at end of file
diff --git a/libs/nativehelper_jvm/file_descriptor_jni.c b/libs/nativehelper_jvm/file_descriptor_jni.c
new file mode 100644
index 0000000..36880cd
--- /dev/null
+++ b/libs/nativehelper_jvm/file_descriptor_jni.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <android/file_descriptor_jni.h>
+
+#include <stddef.h>
+
+#define LOG_TAG "file_descriptor_jni"
+#include <log/log.h>
+
+#include "JniConstants.h"
+
+static void EnsureArgumentIsFileDescriptor(JNIEnv* env, jobject instance) {
+    LOG_ALWAYS_FATAL_IF(instance == NULL, "FileDescriptor is NULL");
+    jclass jifd = JniConstants_FileDescriptorClass(env);
+    LOG_ALWAYS_FATAL_IF(!(*env)->IsInstanceOf(env, instance, jifd),
+                         "Argument is not a FileDescriptor");
+}
+
+JNIEXPORT _Nullable jobject AFileDescriptor_create(JNIEnv* env) {
+    return (*env)->NewObject(env,
+                             JniConstants_FileDescriptorClass(env),
+                             JniConstants_FileDescriptor_init(env));
+}
+
+JNIEXPORT int AFileDescriptor_getFd(JNIEnv* env, jobject fileDescriptor) {
+    EnsureArgumentIsFileDescriptor(env, fileDescriptor);
+    return (*env)->GetIntField(env, fileDescriptor, JniConstants_FileDescriptor_fd(env));
+}
+
+JNIEXPORT void AFileDescriptor_setFd(JNIEnv* env, jobject fileDescriptor, int fd) {
+    EnsureArgumentIsFileDescriptor(env, fileDescriptor);
+    (*env)->SetIntField(env, fileDescriptor, JniConstants_FileDescriptor_fd(env), fd);
+}