Merge "[Ravenwood] Use native system property implementation" into main
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 90bb93d..6e73b2c 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -31,6 +31,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;
@@ -38,6 +39,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;
@@ -68,6 +70,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
@@ -118,6 +122,7 @@
     }
 
     private static RavenwoodConfig sConfig;
+    private static RavenwoodSystemProperties sProps;
     private static boolean sInitialized = false;
 
     /**
@@ -132,6 +137,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);
 
@@ -355,12 +368,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 989bb6b..ef795c6 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";
 
@@ -110,29 +107,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";
 
@@ -140,22 +129,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;
+}