[Ravenwood] Use native system property implementation

System properties are not only used by Java code, but also native code.
To make the property values consistent across Java and native code, make
the "source of truth" of property values on the native side.

In order to achieve this, we have to introduce a new native library
"libravenwood_sysprops" that does NOT link against libbase, and load
that library first. By doing so, we can override the low-level sysprop
function symbols with our own implementation.

Once that is done, all existing native code accessing system properties,
regardless whether they use the libbase/libcutils wrappers or the raw
sysprop functions will go through Ravenwood's implementation.

Other than improving system properties, this provides the infrastructure
to override/implement C functions that is used in native code.

Bug: 292141694
Flag: EXEMPT host test change only
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh
Change-Id: I14678e2ac52ace0b23bd53df7b6092a1cbb881b3
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index e53873b..89b727c 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -21,13 +21,10 @@
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodRedirect;
-import android.ravenwood.annotation.RavenwoodRedirectionClass;
 import android.util.Log;
 import android.util.MutableInt;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.ravenwood.RavenwoodEnvironment;
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
@@ -40,8 +37,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Predicate;
 
 /**
  * Gives access to the system properties store.  The system properties
@@ -58,7 +53,6 @@
  */
 @SystemApi
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("SystemProperties_host")
 public class SystemProperties {
     private static final String TAG = "SystemProperties";
     private static final boolean TRACK_KEY_ACCESS = false;
@@ -76,7 +70,7 @@
 
     @UnsupportedAppUsage
     @GuardedBy("sChangeCallbacks")
-    static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();
+    private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();
 
     @GuardedBy("sRoReads")
     private static final HashMap<String, MutableInt> sRoReads =
@@ -102,19 +96,6 @@
         }
     }
 
-    /** @hide */
-    @RavenwoodRedirect
-    public static void init$ravenwood(Map<String, String> values,
-            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
-        throw RavenwoodEnvironment.notSupportedOnDevice();
-    }
-
-    /** @hide */
-    @RavenwoodRedirect
-    public static void reset$ravenwood() {
-        throw RavenwoodEnvironment.notSupportedOnDevice();
-    }
-
     // The one-argument version of native_get used to be a regular native function. Nowadays,
     // we use the two-argument form of native_get all the time, but we can't just delete the
     // one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation
@@ -126,46 +107,34 @@
 
     @FastNative
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-    @RavenwoodRedirect
     private static native String native_get(String key, String def);
     @FastNative
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-    @RavenwoodRedirect
     private static native int native_get_int(String key, int def);
     @FastNative
     @UnsupportedAppUsage
-    @RavenwoodRedirect
     private static native long native_get_long(String key, long def);
     @FastNative
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-    @RavenwoodRedirect
     private static native boolean native_get_boolean(String key, boolean def);
 
     @FastNative
-    @RavenwoodRedirect
     private static native long native_find(String name);
     @FastNative
-    @RavenwoodRedirect
     private static native String native_get(long handle);
     @CriticalNative
-    @RavenwoodRedirect
     private static native int native_get_int(long handle, int def);
     @CriticalNative
-    @RavenwoodRedirect
     private static native long native_get_long(long handle, long def);
     @CriticalNative
-    @RavenwoodRedirect
     private static native boolean native_get_boolean(long handle, boolean def);
 
     // _NOT_ FastNative: native_set performs IPC and can block
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-    @RavenwoodRedirect
     private static native void native_set(String key, String def);
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-    @RavenwoodRedirect
     private static native void native_add_change_callback();
-    @RavenwoodRedirect
     private static native void native_report_sysprop_change();
 
     /**
@@ -301,7 +270,7 @@
     }
 
     @SuppressWarnings("unused")  // Called from native code.
-    static void callChangeCallbacks() {
+    private static void callChangeCallbacks() {
         ArrayList<Runnable> callbacks = null;
         synchronized (sChangeCallbacks) {
             //Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!");
@@ -327,6 +296,16 @@
     }
 
     /**
+     * Clear all callback changes.
+     * @hide
+     */
+    public static void clearChangeCallbacksForTest() {
+        synchronized (sChangeCallbacks) {
+            sChangeCallbacks.clear();
+        }
+    }
+
+    /**
      * Notifies listeners that a system property has changed
      * @hide
      */
diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp
index 88e6fa3..e99d890 100644
--- a/core/jni/android_os_SystemProperties.cpp
+++ b/core/jni/android_os_SystemProperties.cpp
@@ -34,8 +34,6 @@
 
 #if defined(__BIONIC__)
 # include <sys/system_properties.h>
-#else
-struct prop_info;
 #endif
 
 namespace android {
@@ -46,7 +44,6 @@
 template<typename Functor>
 void ReadProperty(const prop_info* prop, Functor&& functor)
 {
-#if defined(__BIONIC__)
     auto thunk = [](void* cookie,
                     const char* /*name*/,
                     const char* value,
@@ -54,9 +51,6 @@
         std::forward<Functor>(*static_cast<Functor*>(cookie))(value);
     };
     __system_property_read_callback(prop, thunk, &functor);
-#else
-    LOG(FATAL) << "fast property access supported only on device";
-#endif
 }
 
 template<typename Functor>
@@ -66,16 +60,11 @@
     if (!key.c_str()) {
         return;
     }
-#if defined(__BIONIC__)
     const prop_info* prop = __system_property_find(key.c_str());
     if (!prop) {
         return;
     }
     ReadProperty(prop, std::forward<Functor>(functor));
-#else
-    std::forward<Functor>(functor)(
-        android::base::GetProperty(key.c_str(), "").c_str());
-#endif
 }
 
 jstring SystemProperties_getSS(JNIEnv* env, jclass clazz, jstring keyJ,
@@ -132,17 +121,12 @@
 
 jlong SystemProperties_find(JNIEnv* env, jclass, jstring keyJ)
 {
-#if defined(__BIONIC__)
     ScopedUtfChars key(env, keyJ);
     if (!key.c_str()) {
         return 0;
     }
     const prop_info* prop = __system_property_find(key.c_str());
     return reinterpret_cast<jlong>(prop);
-#else
-    LOG(FATAL) << "fast property access supported only on device";
-    __builtin_unreachable();  // Silence warning
-#endif
 }
 
 jstring SystemProperties_getH(JNIEnv* env, jclass clazz, jlong propJ)
@@ -198,11 +182,7 @@
     // request" failures).
     errno = 0;
     bool success;
-#if defined(__BIONIC__)
     success = !__system_property_set(key.c_str(), value_c_str);
-#else
-    success = android::base::SetProperty(key.c_str(), value_c_str);
-#endif
     if (!success) {
         if (errno != 0) {
             jniThrowExceptionFmt(env, "java/lang/RuntimeException",
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index d98120f..75aca1b 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -23,11 +23,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.platform.test.ravenwood.RavenwoodRule;
+import android.platform.test.ravenwood.RavenwoodConfig;
 
 import androidx.test.filters.SmallTest;
 
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Objects;
@@ -35,16 +34,18 @@
 import java.util.concurrent.TimeUnit;
 
 public class SystemPropertiesTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
-            .setSystemPropertyMutable(KEY, null)
-            .setSystemPropertyMutable(UNSET_KEY, null)
-            .setSystemPropertyMutable(PERSIST_KEY, null)
-            .build();
-
     private static final String KEY = "sys.testkey";
     private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
     private static final String PERSIST_KEY = "persist.sys.testkey";
+    private static final String NONEXIST_KEY = "doesnotexist_2341431";
+
+    @RavenwoodConfig.Config
+    public static final RavenwoodConfig mRavenwood = new RavenwoodConfig.Builder()
+            .setSystemPropertyMutable(KEY, null)
+            .setSystemPropertyMutable(UNSET_KEY, null)
+            .setSystemPropertyMutable(PERSIST_KEY, null)
+            .setSystemPropertyImmutable(NONEXIST_KEY, null)
+            .build();
 
     @Test
     @SmallTest
@@ -117,7 +118,7 @@
     @SmallTest
     public void testHandle() throws Exception {
         String value;
-        SystemProperties.Handle handle = SystemProperties.find("doesnotexist_2341431");
+        SystemProperties.Handle handle = SystemProperties.find(NONEXIST_KEY);
         assertNull(handle);
         SystemProperties.set(KEY, "abc");
         handle = SystemProperties.find(KEY);
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 10e4f38..6b1197a 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -267,25 +267,39 @@
     visibility: ["//frameworks/base"],
 }
 
-cc_library_shared {
-    name: "libravenwood_runtime",
-    host_supported: true,
-
+cc_defaults {
+    name: "ravenwood_jni_defaults",
     cflags: [
         "-Wall",
         "-Werror",
         "-Wno-unused-parameter",
         "-Wthread-safety",
     ],
-
-    srcs: [
-        "runtime-jni/*.cpp",
+    static_libs: [
+        "libnativehelper_jvm",
     ],
+    shared_libs: [
+        "liblog",
+    ],
+}
 
+// We need this as a separate library because we need to overload the
+// sysprop symbols before libbase is loaded into the process
+cc_library_host_shared {
+    name: "libravenwood_sysprop",
+    defaults: ["ravenwood_jni_defaults"],
+    srcs: ["runtime-jni/ravenwood_sysprop.cpp"],
+}
+
+cc_library_host_shared {
+    name: "libravenwood_runtime",
+    defaults: ["ravenwood_jni_defaults"],
+    srcs: [
+        "runtime-jni/ravenwood_runtime.cpp",
+        "runtime-jni/ravenwood_os_constants.cpp",
+    ],
     shared_libs: [
         "libbase",
-        "liblog",
-        "libnativehelper",
         "libutils",
         "libcutils",
     ],
@@ -377,8 +391,10 @@
         "z00-all-updatable-modules-system-stubs",
     ],
     jni_libs: [
-        "libandroid_runtime",
+        // Libraries has to be loaded in the following order
+        "libravenwood_sysprop",
         "libravenwood_runtime",
+        "libandroid_runtime",
     ],
 }
 
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
index e548611..d29b93c 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
@@ -43,6 +43,7 @@
             android.util.Log.class,
             android.os.Parcel.class,
             android.os.Binder.class,
+            android.os.SystemProperties.class,
             android.content.res.ApkAssets.class,
             android.content.res.AssetManager.class,
             android.content.res.StringBlock.class,
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 2417262..ea8ebd9 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -32,6 +32,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
@@ -39,6 +40,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.os.RuntimeInit;
+import com.android.ravenwood.RavenwoodRuntimeNative;
 import com.android.ravenwood.common.RavenwoodCommonUtils;
 import com.android.ravenwood.common.RavenwoodRuntimeException;
 import com.android.ravenwood.common.SneakyThrow;
@@ -69,6 +71,8 @@
     }
 
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
+    private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop";
+    private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
 
     /**
      * When enabled, attempt to dump all thread stacks just before we hit the
@@ -119,6 +123,7 @@
     }
 
     private static RavenwoodConfig sConfig;
+    private static RavenwoodSystemProperties sProps;
     private static boolean sInitialized = false;
 
     /**
@@ -133,6 +138,14 @@
         // We haven't initialized liblog yet, so directly write to System.out here.
         RavenwoodCommonUtils.log(TAG, "globalInit()");
 
+        // Load libravenwood_sysprop first
+        var libProp = RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_SYSPROP_NAME);
+        System.load(libProp);
+        RavenwoodRuntimeNative.reloadNativeLibrary(libProp);
+
+        // Make sure libravenwood_runtime is loaded.
+        System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
+
         // Do the basic set up for the android sysprops.
         setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES);
 
@@ -358,12 +371,21 @@
     /**
      * Set the current configuration to the actual SystemProperties.
      */
-    public static void setSystemProperties(RavenwoodSystemProperties ravenwoodSystemProperties) {
-        var clone = new RavenwoodSystemProperties(ravenwoodSystemProperties, true);
+    private static void setSystemProperties(RavenwoodSystemProperties systemProperties) {
+        SystemProperties.clearChangeCallbacksForTest();
+        RavenwoodRuntimeNative.clearSystemProperties();
+        sProps = new RavenwoodSystemProperties(systemProperties, true);
+        for (var entry : systemProperties.getValues().entrySet()) {
+            RavenwoodRuntimeNative.setSystemProperty(entry.getKey(), entry.getValue());
+        }
+    }
 
-        android.os.SystemProperties.init$ravenwood(
-                clone.getValues(),
-                clone.getKeyReadablePredicate(),
-                clone.getKeyWritablePredicate());
+    @SuppressWarnings("unused")  // Called from native code (ravenwood_sysprop.cpp)
+    private static void checkSystemPropertyAccess(String key, boolean write) {
+        boolean result = write ? sProps.isKeyWritable(key) : sProps.isKeyReadable(key);
+        if (!result) {
+            throw new IllegalArgumentException((write ? "Write" : "Read")
+                    + " access to system property '" + key + "' denied via RavenwoodConfig");
+        }
     }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index ef8f584..f1e1ef6 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -22,7 +22,6 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Predicate;
 
 public class RavenwoodSystemProperties {
     private volatile boolean mIsImmutable;
@@ -31,47 +30,9 @@
 
     /** Set of additional keys that should be considered readable */
     private final Set<String> mKeyReadable = new HashSet<>();
-    private final Predicate<String> mKeyReadablePredicate = (key) -> {
-        final String root = getKeyRoot(key);
-
-        if (root.startsWith("debug.")) return true;
-
-        // This set is carefully curated to help identify situations where a test may
-        // accidentally depend on a default value of an obscure property whose owner hasn't
-        // decided how Ravenwood should behave.
-        if (root.startsWith("boot.")) return true;
-        if (root.startsWith("build.")) return true;
-        if (root.startsWith("product.")) return true;
-        if (root.startsWith("soc.")) return true;
-        if (root.startsWith("system.")) return true;
-
-        switch (key) {
-            case "gsm.version.baseband":
-            case "no.such.thing":
-            case "qemu.sf.lcd_density":
-            case "ro.bootloader":
-            case "ro.debuggable":
-            case "ro.hardware":
-            case "ro.hw_timeout_multiplier":
-            case "ro.odm.build.media_performance_class":
-            case "ro.sf.lcd_density":
-            case "ro.treble.enabled":
-            case "ro.vndk.version":
-                return true;
-        }
-
-        return mKeyReadable.contains(key);
-    };
 
     /** Set of additional keys that should be considered writable */
     private final Set<String> mKeyWritable = new HashSet<>();
-    private final Predicate<String> mKeyWritablePredicate = (key) -> {
-        final String root = getKeyRoot(key);
-
-        if (root.startsWith("debug.")) return true;
-
-        return mKeyWritable.contains(key);
-    };
 
     public RavenwoodSystemProperties() {
         // TODO: load these values from build.prop generated files
@@ -121,12 +82,45 @@
         return new HashMap<>(mValues);
     }
 
-    public Predicate<String> getKeyReadablePredicate() {
-        return mKeyReadablePredicate;
+    public boolean isKeyReadable(String key) {
+        final String root = getKeyRoot(key);
+
+        if (root.startsWith("debug.")) return true;
+
+        // This set is carefully curated to help identify situations where a test may
+        // accidentally depend on a default value of an obscure property whose owner hasn't
+        // decided how Ravenwood should behave.
+        if (root.startsWith("boot.")) return true;
+        if (root.startsWith("build.")) return true;
+        if (root.startsWith("product.")) return true;
+        if (root.startsWith("soc.")) return true;
+        if (root.startsWith("system.")) return true;
+
+        switch (key) {
+            case "gsm.version.baseband":
+            case "no.such.thing":
+            case "qemu.sf.lcd_density":
+            case "ro.bootloader":
+            case "ro.debuggable":
+            case "ro.hardware":
+            case "ro.hw_timeout_multiplier":
+            case "ro.odm.build.media_performance_class":
+            case "ro.sf.lcd_density":
+            case "ro.treble.enabled":
+            case "ro.vndk.version":
+            case "ro.icu.data.path":
+                return true;
+        }
+
+        return mKeyReadable.contains(key);
     }
 
-    public Predicate<String> getKeyWritablePredicate() {
-        return mKeyWritablePredicate;
+    public boolean isKeyWritable(String key) {
+        final String root = getKeyRoot(key);
+
+        if (root.startsWith("debug.")) return true;
+
+        return mKeyWritable.contains(key);
     }
 
     private static final String[] PARTITIONS = {
@@ -208,4 +202,4 @@
     // Create a default instance, and make an immutable copy of it.
     public static final RavenwoodSystemProperties DEFAULT_VALUES =
             new RavenwoodSystemProperties(new RavenwoodSystemProperties(), true);
-}
\ No newline at end of file
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 96746c6..af3cc8f 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -47,9 +47,6 @@
     public static final boolean RAVENWOOD_VERBOSE_LOGGING = "1".equals(System.getenv(
             "RAVENWOOD_VERBOSE"));
 
-    /** Name of `libravenwood_runtime` */
-    private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
-
     /** Directory name of `out/host/linux-x86/testcases/ravenwood-runtime` */
     private static final String RAVENWOOD_RUNTIME_DIR_NAME = "ravenwood-runtime";
 
@@ -108,29 +105,21 @@
     }
 
     /**
-     * Load the main runtime JNI library.
-     */
-    public static void loadRavenwoodNativeRuntime() {
-        ensureOnRavenwood();
-        loadJniLibrary(RAVENWOOD_NATIVE_RUNTIME_NAME);
-    }
-
-    /**
      * Internal implementation of
      * {@link android.platform.test.ravenwood.RavenwoodUtils#loadJniLibrary(String)}
      */
     public static void loadJniLibrary(String libname) {
         if (RavenwoodCommonUtils.isOnRavenwood()) {
-            loadJniLibraryInternal(libname);
+            System.load(getJniLibraryPath(libname));
         } else {
             System.loadLibrary(libname);
         }
     }
 
     /**
-     * Function equivalent to ART's System.loadLibrary. See RavenwoodUtils for why we need it.
+     * Find the shared library path from java.library.path.
      */
-    private static void loadJniLibraryInternal(String libname) {
+    public static String getJniLibraryPath(String libname) {
         var path = System.getProperty("java.library.path");
         var filename = "lib" + libname + ".so";
 
@@ -138,22 +127,21 @@
 
         try {
             if (path == null) {
-                throw new UnsatisfiedLinkError("Cannot load library " + libname + "."
+                throw new UnsatisfiedLinkError("Cannot find library " + libname + "."
                         + " Property java.library.path not set!");
             }
             for (var dir : path.split(":")) {
                 var file = new File(dir + "/" + filename);
                 if (file.exists()) {
-                    System.load(file.getAbsolutePath());
-                    return;
+                    return file.getAbsolutePath();
                 }
             }
-            throw new UnsatisfiedLinkError("Library " + libname + " not found in "
-                    + "java.library.path: " + path);
         } catch (Throwable e) {
             dumpFiles(System.out);
             throw e;
         }
+        throw new UnsatisfiedLinkError("Library " + libname + " not found in "
+                + "java.library.path: " + path);
     }
 
     private static void dumpFiles(PrintStream out) {
diff --git a/ravenwood/runtime-helper-src/framework/android/os/SystemProperties_host.java b/ravenwood/runtime-helper-src/framework/android/os/SystemProperties_host.java
deleted file mode 100644
index b09bf31..0000000
--- a/ravenwood/runtime-helper-src/framework/android/os/SystemProperties_host.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2023 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 android.os;
-
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-
-import java.util.Map;
-import java.util.Objects;
-import java.util.function.Predicate;
-
-public class SystemProperties_host {
-    private static final Object sLock = new Object();
-
-    /** Active system property values */
-    @GuardedBy("sLock")
-    private static Map<String, String> sValues;
-    /** Predicate tested to determine if a given key can be read. */
-    @GuardedBy("sLock")
-    private static Predicate<String> sKeyReadablePredicate;
-    /** Predicate tested to determine if a given key can be written. */
-    @GuardedBy("sLock")
-    private static Predicate<String> sKeyWritablePredicate;
-
-    /**
-     * Reverse mapping that provides a way back to an original key from the
-     * {@link System#identityHashCode(Object)} of {@link String#intern}.
-     */
-    @GuardedBy("sLock")
-    private static SparseArray<String> sKeyHandles = new SparseArray<>();
-
-    /**
-     * Basically the same as {@link #init$ravenwood}, but it'll only run if no values are
-     * set yet.
-     */
-    public static void initializeIfNeeded(Map<String, String> values,
-            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
-        synchronized (sLock) {
-            if (sValues != null) {
-                return; // Already initialized.
-            }
-            init$ravenwood(values, keyReadablePredicate, keyWritablePredicate);
-        }
-    }
-
-    public static void init$ravenwood(Map<String, String> values,
-            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
-        synchronized (sLock) {
-            sValues = Objects.requireNonNull(values);
-            sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate);
-            sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate);
-            sKeyHandles.clear();
-            synchronized (SystemProperties.sChangeCallbacks) {
-                SystemProperties.sChangeCallbacks.clear();
-            }
-        }
-    }
-
-    public static void reset$ravenwood() {
-        synchronized (sLock) {
-            sValues = null;
-            sKeyReadablePredicate = null;
-            sKeyWritablePredicate = null;
-            sKeyHandles.clear();
-            synchronized (SystemProperties.sChangeCallbacks) {
-                SystemProperties.sChangeCallbacks.clear();
-            }
-        }
-    }
-
-    public static void native_set(String key, String val) {
-        synchronized (sLock) {
-            Objects.requireNonNull(key);
-            Preconditions.requireNonNullViaRavenwoodRule(sValues);
-            if (!sKeyWritablePredicate.test(key)) {
-                throw new IllegalArgumentException(
-                        "Write access to system property '" + key + "' denied via RavenwoodRule");
-            }
-            if (key.startsWith("ro.") && sValues.containsKey(key)) {
-                throw new IllegalArgumentException(
-                        "System property '" + key + "' already defined once; cannot redefine");
-            }
-            if ((val == null) || val.isEmpty()) {
-                sValues.remove(key);
-            } else {
-                sValues.put(key, val);
-            }
-            SystemProperties.callChangeCallbacks();
-        }
-    }
-
-    public static String native_get(String key, String def) {
-        synchronized (sLock) {
-            Objects.requireNonNull(key);
-            Preconditions.requireNonNullViaRavenwoodRule(sValues);
-            if (!sKeyReadablePredicate.test(key)) {
-                throw new IllegalArgumentException(
-                        "Read access to system property '" + key + "' denied via RavenwoodRule");
-            }
-            return sValues.getOrDefault(key, def);
-        }
-    }
-
-    public static int native_get_int(String key, int def) {
-        try {
-            return Integer.parseInt(native_get(key, ""));
-        } catch (NumberFormatException ignored) {
-            return def;
-        }
-    }
-
-    public static long native_get_long(String key, long def) {
-        try {
-            return Long.parseLong(native_get(key, ""));
-        } catch (NumberFormatException ignored) {
-            return def;
-        }
-    }
-
-    public static boolean native_get_boolean(String key, boolean def) {
-        return parseBoolean(native_get(key, ""), def);
-    }
-
-    public static long native_find(String name) {
-        synchronized (sLock) {
-            Preconditions.requireNonNullViaRavenwoodRule(sValues);
-            if (sValues.containsKey(name)) {
-                name = name.intern();
-                final int handle = System.identityHashCode(name);
-                sKeyHandles.put(handle, name);
-                return handle;
-            } else {
-                return 0;
-            }
-        }
-    }
-
-    public static String native_get(long handle) {
-        synchronized (sLock) {
-            return native_get(sKeyHandles.get((int) handle), "");
-        }
-    }
-
-    public static int native_get_int(long handle, int def) {
-        synchronized (sLock) {
-            return native_get_int(sKeyHandles.get((int) handle), def);
-        }
-    }
-
-    public static long native_get_long(long handle, long def) {
-        synchronized (sLock) {
-            return native_get_long(sKeyHandles.get((int) handle), def);
-        }
-    }
-
-    public static boolean native_get_boolean(long handle, boolean def) {
-        synchronized (sLock) {
-            return native_get_boolean(sKeyHandles.get((int) handle), def);
-        }
-    }
-
-    public static void native_add_change_callback() {
-        // Ignored; callback always registered via init above
-    }
-
-    public static void native_report_sysprop_change() {
-        // Report through callback always registered via init above
-        synchronized (sLock) {
-            Preconditions.requireNonNullViaRavenwoodRule(sValues);
-            SystemProperties.callChangeCallbacks();
-        }
-    }
-
-    private static boolean parseBoolean(String val, boolean def) {
-        // Matches system/libbase/include/android-base/parsebool.h
-        if (val == null) return def;
-        switch (val) {
-            case "1":
-            case "on":
-            case "true":
-            case "y":
-            case "yes":
-                return true;
-            case "0":
-            case "false":
-            case "n":
-            case "no":
-            case "off":
-                return false;
-            default:
-                return def;
-        }
-    }
-}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/OsConstants.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/OsConstants.java
index c56ec8a..3fedc1a 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/OsConstants.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/OsConstants.java
@@ -15,8 +15,6 @@
  */
 package android.system;
 
-import com.android.ravenwood.common.RavenwoodCommonUtils;
-
 /**
  * Copied from libcore's version, with the local changes:
  * - All the imports are removed. (they're only used in javadoc)
@@ -1252,8 +1250,6 @@
     private static int placeholder() { return 0; }
     // ...because we want to initialize them at runtime.
     static {
-        // [ravenwood-change] Load the JNI lib.
-        RavenwoodCommonUtils.loadRavenwoodNativeRuntime();
         Native.initConstants();
     }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index ad80d92..f13189f 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -19,7 +19,6 @@
 import android.system.StructStat;
 
 import com.android.ravenwood.common.JvmWorkaround;
-import com.android.ravenwood.common.RavenwoodCommonUtils;
 
 import java.io.FileDescriptor;
 
@@ -30,11 +29,6 @@
     private RavenwoodRuntimeNative() {
     }
 
-    static {
-        RavenwoodCommonUtils.ensureOnRavenwood();
-        RavenwoodCommonUtils.loadRavenwoodNativeRuntime();
-    }
-
     public static native void applyFreeFunction(long freeFunction, long nativePtr);
 
     private static native long nLseek(int fd, long offset, int whence) throws ErrnoException;
@@ -56,6 +50,14 @@
     public static native void setenv(String name, String value, boolean overwrite)
             throws ErrnoException;
 
+    public static native void reloadNativeLibrary(String libFile);
+
+    public static native String getSystemProperty(String key);
+
+    public static native boolean setSystemProperty(String key, String value);
+
+    public static native void clearSystemProperties();
+
     public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
         return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
     }
diff --git a/ravenwood/runtime-jni/jni_helper.h b/ravenwood/runtime-jni/jni_helper.h
new file mode 100644
index 0000000..561fb3b
--- /dev/null
+++ b/ravenwood/runtime-jni/jni_helper.h
@@ -0,0 +1,99 @@
+/*
+ * 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 <jni.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+#include <string>
+
+constexpr const char* kCommonUtils = "com/android/ravenwood/common/RavenwoodCommonUtils";
+constexpr const char* kRuntimeEnvController =
+        "android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController";
+constexpr const char* kRuntimeNative = "com/android/ravenwood/RavenwoodRuntimeNative";
+
+// We have to explicitly decode the string to real UTF-8, because when using GetStringUTFChars
+// we only get modified UTF-8, which is not the platform string type used in host JVM.
+struct ScopedRealUtf8Chars {
+    ScopedRealUtf8Chars(JNIEnv* env, jstring s) : valid_(false) {
+        if (s == nullptr) {
+            jniThrowNullPointerException(env);
+            return;
+        }
+        jclass clazz = env->GetObjectClass(s);
+        jmethodID getBytes = env->GetMethodID(clazz, "getBytes", "(Ljava/lang/String;)[B");
+
+        ScopedLocalRef<jstring> utf8(env, env->NewStringUTF("UTF-8"));
+        ScopedLocalRef<jbyteArray> jbytes(env,
+                                          (jbyteArray)env->CallObjectMethod(s, getBytes,
+                                                                            utf8.get()));
+
+        ScopedByteArrayRO bytes(env, jbytes.get());
+        string_.append((const char*)bytes.get(), bytes.size());
+        valid_ = true;
+    }
+
+    const char* c_str() const {
+        return valid_ ? string_.c_str() : nullptr;
+    }
+
+    size_t size() const {
+        return string_.size();
+    }
+
+    const char& operator[](size_t n) const {
+        return string_[n];
+    }
+
+private:
+    std::string string_;
+    bool valid_;
+};
+
+static inline JNIEnv* GetJNIEnvOrDie(JavaVM* vm) {
+    JNIEnv* env = nullptr;
+    vm->GetEnv((void**)&env, JNI_VERSION_1_4);
+    LOG_ALWAYS_FATAL_IF(env == nullptr, "Could not retrieve JNIEnv.");
+    return env;
+}
+
+static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
+    jclass clazz = env->FindClass(class_name);
+    LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name);
+    return clazz;
+}
+
+template <typename T>
+static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
+    jobject res = env->NewGlobalRef(in);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference.");
+    return static_cast<T>(res);
+}
+
+static inline jclass FindGlobalClassOrDie(JNIEnv* env, const char* class_name) {
+    return MakeGlobalRefOrDie(env, FindClassOrDie(env, class_name));
+}
+
+static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
+                                               const char* method_signature) {
+    jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s",
+                        method_name, method_signature);
+    return res;
+}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index c255be5..3ff0848 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -15,21 +15,14 @@
  */
 
 #include <fcntl.h>
-#include <sys/stat.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <unistd.h>
+#include <utils/misc.h>
+
 #include <string>
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include <nativehelper/ScopedUtfChars.h>
-#include <nativehelper/ScopedPrimitiveArray.h>
 
-#include "jni.h"
-#include "utils/Log.h"
-#include "utils/misc.h"
-
-// Defined in ravenwood_os_constants.cpp
-void register_android_system_OsConstants(JNIEnv* env);
+#include "jni_helper.h"
 
 // ---- Exception related ----
 
@@ -51,53 +44,6 @@
 static jclass g_StructStat;
 static jclass g_StructTimespecClass;
 
-// We have to explicitly decode the string to real UTF-8, because when using GetStringUTFChars
-// we only get modified UTF-8, which is not the platform string type used in host JVM.
-struct ScopedRealUtf8Chars {
-    ScopedRealUtf8Chars(JNIEnv* env, jstring s) : valid_(false) {
-        if (s == nullptr) {
-            jniThrowNullPointerException(env);
-            return;
-        }
-        jclass clazz = env->GetObjectClass(s);
-        jmethodID getBytes = env->GetMethodID(clazz, "getBytes", "(Ljava/lang/String;)[B");
-
-        ScopedLocalRef<jstring> utf8(env, env->NewStringUTF("UTF-8"));
-        ScopedLocalRef<jbyteArray> jbytes(env,
-            (jbyteArray) env->CallObjectMethod(s, getBytes, utf8.get()));
-
-        ScopedByteArrayRO bytes(env, jbytes.get());
-        string_.append((const char *) bytes.get(), bytes.size());
-        valid_ = true;
-    }
-
-    const char* c_str() const {
-        return valid_ ? string_.c_str() : nullptr;
-    }
-
-    size_t size() const {
-        return string_.size();
-    }
-
-    const char& operator[](size_t n) const {
-        return string_[n];
-    }
-
-private:
-    std::string string_;
-    bool valid_;
-};
-
-static jclass findClass(JNIEnv* env, const char* name) {
-    ScopedLocalRef<jclass> localClass(env, env->FindClass(name));
-    jclass result = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
-    if (result == NULL) {
-        ALOGE("failed to find class '%s'", name);
-        abort();
-    }
-    return result;
-}
-
 static jobject makeStructTimespec(JNIEnv* env, const struct timespec& ts) {
     static jmethodID ctor = env->GetMethodID(g_StructTimespecClass, "<init>",
             "(JJ)V");
@@ -229,6 +175,8 @@
 
 // ---- Registration ----
 
+extern void register_android_system_OsConstants(JNIEnv* env);
+
 static const JNINativeMethod sMethods[] =
 {
     { "applyFreeFunction", "(JJ)V", (void*)nApplyFreeFunction },
@@ -243,24 +191,14 @@
     { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
 };
 
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
-{
-    JNIEnv* env = NULL;
-    jint result = -1;
-
-    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
-        ALOGE("GetEnv failed!");
-        return result;
-    }
-    ALOG_ASSERT(env, "Could not retrieve the env!");
-
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
     ALOGI("%s: JNI_OnLoad", __FILE__);
 
-    g_StructStat = findClass(env, "android/system/StructStat");
-    g_StructTimespecClass = findClass(env, "android/system/StructTimespec");
+    JNIEnv* env = GetJNIEnvOrDie(vm);
+    g_StructStat = FindGlobalClassOrDie(env, "android/system/StructStat");
+    g_StructTimespecClass = FindGlobalClassOrDie(env, "android/system/StructTimespec");
 
-    jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/RavenwoodRuntimeNative",
-            sMethods, NELEM(sMethods));
+    jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods));
     if (res < 0) {
         return res;
     }
diff --git a/ravenwood/runtime-jni/ravenwood_sysprop.cpp b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
new file mode 100644
index 0000000..4fb61b6
--- /dev/null
+++ b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
@@ -0,0 +1,187 @@
+/*
+ * 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 <dlfcn.h>
+
+#include <set>
+
+#include "jni_helper.h"
+
+// Implement a rudimentary system properties data store
+
+#define PROP_VALUE_MAX 92
+
+namespace {
+
+struct prop_info {
+    std::string key;
+    mutable std::string value;
+    mutable uint32_t serial;
+
+    prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {}
+};
+
+struct prop_info_cmp {
+    using is_transparent = void;
+    bool operator()(const prop_info& lhs, const prop_info& rhs) {
+        return lhs.key < rhs.key;
+    }
+    bool operator()(std::string_view lhs, const prop_info& rhs) {
+        return lhs < rhs.key;
+    }
+    bool operator()(const prop_info& lhs, std::string_view rhs) {
+        return lhs.key < rhs;
+    }
+};
+
+} // namespace
+
+static auto& g_properties_lock = *new std::mutex;
+static auto& g_properties = *new std::set<prop_info, prop_info_cmp>;
+
+static bool property_set(const char* key, const char* value) {
+    if (key == nullptr || *key == '\0') return false;
+    if (value == nullptr) value = "";
+    bool read_only = !strncmp(key, "ro.", 3);
+    if (!read_only && strlen(value) >= PROP_VALUE_MAX) return -1;
+
+    std::lock_guard lock(g_properties_lock);
+    auto [it, success] = g_properties.emplace(key, value);
+    if (read_only) return success;
+    if (!success) {
+        it->value = value;
+        ++it->serial;
+    }
+    return true;
+}
+
+template <typename Func>
+static void property_get(const char* key, Func callback) {
+    std::lock_guard lock(g_properties_lock);
+    auto it = g_properties.find(key);
+    if (it != g_properties.end()) {
+        callback(*it);
+    }
+}
+
+// Redefine the __system_property_XXX functions here so we can perform
+// logging and access checks for all sysprops in native code.
+
+static void check_system_property_access(const char* key, bool write);
+
+extern "C" {
+
+int __system_property_set(const char* key, const char* value) {
+    check_system_property_access(key, true);
+    return property_set(key, value) ? 0 : -1;
+}
+
+int __system_property_get(const char* key, char* value) {
+    check_system_property_access(key, false);
+    *value = '\0';
+    property_get(key, [&](const prop_info& info) {
+        snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str());
+    });
+    return strlen(value);
+}
+
+const prop_info* __system_property_find(const char* key) {
+    check_system_property_access(key, false);
+    const prop_info* pi = nullptr;
+    property_get(key, [&](const prop_info& info) { pi = &info; });
+    return pi;
+}
+
+void __system_property_read_callback(const prop_info* pi,
+                                     void (*callback)(void*, const char*, const char*, uint32_t),
+                                     void* cookie) {
+    std::lock_guard lock(g_properties_lock);
+    callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial);
+}
+
+} // extern "C"
+
+// ---- JNI ----
+
+static JavaVM* gVM = nullptr;
+static jclass gEnvController = nullptr;
+static jmethodID gCheckSystemPropertyAccess;
+
+static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
+    ScopedUtfChars path(env, javaPath);
+    // Force reload ourselves as global
+    dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD);
+}
+
+// Call back into Java code to check property access
+static void check_system_property_access(const char* key, bool write) {
+    if (gVM != nullptr && gEnvController != nullptr) {
+        JNIEnv* env;
+        if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
+            ALOGI("%s access to system property '%s'", write ? "Write" : "Read", key);
+            env->CallStaticVoidMethod(gEnvController, gCheckSystemPropertyAccess,
+                                      env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
+            return;
+        }
+    }
+    // Not on JVM thread, abort
+    LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key);
+}
+
+static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
+    ScopedUtfChars key(env, javaKey);
+    jstring value = nullptr;
+    property_get(key.c_str(),
+                 [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); });
+    return value;
+}
+
+static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) {
+    ScopedUtfChars key(env, javaKey);
+    ScopedUtfChars value(env, javaValue);
+    return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
+}
+
+static void clearSystemProperties(JNIEnv*, jclass) {
+    std::lock_guard lock(g_properties_lock);
+    g_properties.clear();
+}
+
+static const JNINativeMethod sMethods[] = {
+        {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
+        {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
+        {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
+        {"clearSystemProperties", "()V", (void*)clearSystemProperties},
+};
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+    ALOGI("%s: JNI_OnLoad", __FILE__);
+
+    JNIEnv* env = GetJNIEnvOrDie(vm);
+    gVM = vm;
+
+    // Fetch several references for future use
+    gEnvController = FindGlobalClassOrDie(env, kRuntimeEnvController);
+    gCheckSystemPropertyAccess =
+            GetStaticMethodIDOrDie(env, gEnvController, "checkSystemPropertyAccess",
+                                   "(Ljava/lang/String;Z)V");
+
+    // Expose raw property methods as JNI methods
+    jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods));
+    if (res < 0) return -1;
+
+    return JNI_VERSION_1_4;
+}