Have zygote mount appcompat system properties

If needed, the zygote should mount
/dev/__properties__/appcompat_override in place of /dev/__properties__,
and reload system properties (and any constants derived from them)

Bug: 291814949
Test: atest AppcompatOverrideSystemPropertiesDeclarationTest

Change-Id: Ifd364f5d9463e88b2ee928c1f71a6b96b8c29f45
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index e37b2b5..677143a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -730,13 +730,14 @@
                                                    whitelistedDataInfoMap,
                                            boolean bindMountAppsData,
                                            boolean bindMountAppStorageDirs,
+                                           boolean bindMountSystemOverrides,
                                            @Nullable String[] zygoteArgs) {
         return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
                     zygotePolicyFlags, isTopApp, disabledCompatChanges,
                     pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
-                    bindMountAppStorageDirs, zygoteArgs);
+                    bindMountAppStorageDirs, bindMountSystemOverrides, zygoteArgs);
     }
 
     /** @hide */
@@ -753,6 +754,7 @@
                                                   @Nullable String invokeWith,
                                                   @Nullable String packageName,
                                                   @Nullable long[] disabledCompatChanges,
+                                                  boolean bindMountSyspropOverrides,
                                                   @Nullable String[] zygoteArgs) {
         // Webview zygote can't access app private data files, so doesn't need to know its data
         // info.
@@ -761,7 +763,8 @@
                     abi, instructionSet, appDataDir, invokeWith, packageName,
                     /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
                 disabledCompatChanges, /* pkgDataInfoMap */ null,
-                /* whitelistedDataInfoMap */ null, false, false, zygoteArgs);
+                /* whitelistedDataInfoMap */ null, /* bindMountAppsData */ false,
+                /* bindMountAppStorageDirs */ false, bindMountSyspropOverrides, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 3cb5c60..c14810b 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -355,6 +355,7 @@
                                                           allowlistedDataInfoList,
                                                   boolean bindMountAppsData,
                                                   boolean bindMountAppStorageDirs,
+                                                  boolean bindOverrideSysprops,
                                                   @Nullable String[] zygoteArgs) {
         // TODO (chriswailes): Is there a better place to check this value?
         if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -367,7 +368,7 @@
                     abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
                     packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
                     pkgDataInfoMap, allowlistedDataInfoList, bindMountAppsData,
-                    bindMountAppStorageDirs, zygoteArgs);
+                    bindMountAppStorageDirs, bindOverrideSysprops, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -638,6 +639,7 @@
                                                               allowlistedDataInfoList,
                                                       boolean bindMountAppsData,
                                                       boolean bindMountAppStorageDirs,
+                                                      boolean bindMountOverrideSysprops,
                                                       @Nullable String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
         ArrayList<String> argsForZygote = new ArrayList<>();
@@ -753,6 +755,10 @@
             argsForZygote.add(Zygote.BIND_MOUNT_APP_DATA_DIRS);
         }
 
+        if (bindMountOverrideSysprops) {
+            argsForZygote.add(Zygote.BIND_MOUNT_SYSPROP_OVERRIDES);
+        }
+
         if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
             StringBuilder sb = new StringBuilder();
             sb.append("--disabled-compat-changes=");
@@ -1306,7 +1312,8 @@
                     ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
                     null /* disabledCompatChanges */, null /* pkgDataInfoMap */,
                     null /* allowlistedDataInfoList */, true /* bindMountAppsData*/,
-                    /* bindMountAppStorageDirs */ false, extraArgs);
+                    /* bindMountAppStorageDirs */ false, /*bindMountOverrideSysprops */ false,
+                    extraArgs);
 
         } catch (ZygoteStartFailedEx ex) {
             throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 1c5f4f0..cab84bb 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -236,6 +236,9 @@
     /** Bind mount app storage dirs to lower fs not via fuse */
     public static final String BIND_MOUNT_APP_DATA_DIRS = "--bind-mount-data-dirs";
 
+    /** Bind the system properties to an alternate set, for appcompat reasons */
+    public static final String BIND_MOUNT_SYSPROP_OVERRIDES = "--bind-mount-sysprop-overrides";
+
     /**
      * An extraArg passed when a zygote process is forking a child-zygote, specifying a name
      * in the abstract socket namespace. This socket name is what the new child zygote
@@ -353,6 +356,8 @@
      * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
      * @param bindMountAppDataDirs  True if the zygote needs to mount data dirs.
      * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
+     * @param bindMountSyspropOverrides True if the zygote needs to mount the override system
+     *                                  properties
      *
      * @return 0 if this is the child, pid of the child
      * if this is the parent, or -1 on error.
@@ -361,14 +366,15 @@
             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
             boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList,
-            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
+            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs,
+            boolean bindMountSyspropOverrides) {
         ZygoteHooks.preFork();
 
         int pid = nativeForkAndSpecialize(
                 uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                 fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
                 pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs,
-                bindMountAppStorageDirs);
+                bindMountAppStorageDirs, bindMountSyspropOverrides);
         if (pid == 0) {
             // Note that this event ends at the end of handleChildProc,
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -391,7 +397,7 @@
             int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
             String appDataDir, boolean isTopApp, String[] pkgDataInfoList,
             String[] allowlistedDataInfoList, boolean bindMountAppDataDirs,
-            boolean bindMountAppStorageDirs);
+            boolean bindMountAppStorageDirs, boolean bindMountSyspropOverrides);
 
     /**
      * Specialize an unspecialized app process.  The current VM must have been started
@@ -421,16 +427,19 @@
      * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
      * @param bindMountAppDataDirs  True if the zygote needs to mount data dirs.
      * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
+     * @param bindMountSyspropOverrides True if the zygote needs to mount the override system
+     *                                  properties
      */
     private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
             String[] pkgDataInfoList, String[] allowlistedDataInfoList,
-            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
+            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs,
+            boolean bindMountSyspropOverrides) {
         nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
                 niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
                 pkgDataInfoList, allowlistedDataInfoList,
-                bindMountAppDataDirs, bindMountAppStorageDirs);
+                bindMountAppDataDirs, bindMountAppStorageDirs, bindMountSyspropOverrides);
 
         // Note that this event ends at the end of handleChildProc.
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -455,7 +464,8 @@
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
             String[] pkgDataInfoList, String[] allowlistedDataInfoList,
-            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs);
+            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs,
+            boolean bindMountSyspropOverrides);
 
     /**
      * Called to do any initialization before starting an application.
@@ -866,7 +876,8 @@
                                  args.mSeInfo, args.mNiceName, args.mStartChildZygote,
                                  args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
                                  args.mPkgDataInfoList, args.mAllowlistedDataInfoList,
-                                 args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);
+                                 args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs,
+                                 args.mBindMountSyspropOverrides);
 
             // While `specializeAppProcess` sets the thread name on the process's main thread, this
             // is distinct from the app process name which appears in stack traces, as the latter is
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index ef83982..86b9a59 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -243,6 +243,11 @@
     boolean mBindMountAppDataDirs;
 
     /**
+     * @see Zygote#BIND_MOUNT_SYSPROP_OVERRIDES
+     */
+    boolean mBindMountSyspropOverrides;
+
+    /**
      * Constructs instance and parses args
      *
      * @param args zygote command-line args as ZygoteCommandBuffer, positioned after argument count.
@@ -481,6 +486,8 @@
                 mBindMountAppStorageDirs = true;
             } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) {
                 mBindMountAppDataDirs = true;
+            } else if (arg.equals(Zygote.BIND_MOUNT_SYSPROP_OVERRIDES)) {
+                mBindMountSyspropOverrides = true;
             } else {
                 unprocessedArg = arg;
                 break;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 5fe086d..cbe0700 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -257,7 +257,8 @@
                             parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,
                             parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,
                             parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
-                            parsedArgs.mBindMountAppStorageDirs);
+                            parsedArgs.mBindMountAppStorageDirs,
+                            parsedArgs.mBindMountSyspropOverrides);
 
                     try {
                         if (pid == 0) {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index b12e147..9c1bea7 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -59,6 +59,8 @@
 #include <sys/resource.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <sys/_system_properties.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/un.h>
@@ -1691,6 +1693,131 @@
   fail_fn(CREATE_ERROR("Error dir is not ready %s: %s", dir_path, strerror(errno)));
 }
 
+// All public String android.os.Build constants, and the system properties they're pulled from
+std::pair<const char*, const char*> build_constants[] = {
+        std::pair("ID", "ro.build.id"),
+        std::pair("DISPLAY", "ro.build.display.id"),
+        std::pair("PRODUCT", "ro.product.name"),
+        std::pair("DEVICE", "ro.product.device"),
+        std::pair("BOARD", "ro.product.board"),
+        std::pair("MANUFACTURER", "ro.product.manufacturer"),
+        std::pair("BRAND", "ro.product.brand"),
+        std::pair("MODEL", "ro.product.model"),
+        std::pair("BOOTLOADER", "ro.bootloader"),
+        std::pair("HARDWARE", "ro.hardware"),
+        std::pair("SKU", "ro.boot.hardware.sku"),
+        std::pair("ODM_SKU", "ro.boot.product.hardware.sku"),
+        std::pair("TAGS", "ro.build.tags"),
+        std::pair("TYPE", "ro.build.type"),
+        std::pair("USER", "ro.build.user"),
+        std::pair("HOST", "ro.build.host"),
+};
+
+// All public String Build.VERSION constants, and the system properties they're pulled from
+std::pair<const char*, const char*> build_version_constants[] = {
+        std::pair("INCREMENTAL", "ro.build.version.incremental"),
+        std::pair("RELEASE", "ro.build.version.release"),
+        std::pair("RELEASE_OR_CODENAME", "ro.build.version.release_or_codename"),
+        std::pair("RELEASE_OR_PREVIEW_DISPLAY", "ro.build.version.release_or_preview_display"),
+        std::pair("BASE_OS", "ro.build.version.base_os"),
+        std::pair("SECURITY_PATCH", "ro.build.version.security_patch"),
+        std::pair("SDK", "ro.build.version.sdk"),
+        std::pair("PREVIEW_SDK_FINGERPRINT", "ro.build.version.preview_sdk_fingerprint"),
+        std::pair("CODENAME", "ro.build.version.codename"),
+};
+
+static void ReloadBuildJavaConstant(JNIEnv* env, jclass build_class, const char* field_name,
+                                    const char* field_signature, const char* sysprop_name) {
+  const prop_info* prop_info = __system_property_find(sysprop_name);
+  std::string new_value;
+  __system_property_read_callback(
+          prop_info,
+          [](void* cookie, const char* name, const char* value, unsigned serial) {
+              auto new_value = reinterpret_cast<std::string*>(cookie);
+              *new_value = value;
+          },
+          &new_value);
+  jfieldID fieldId = env->GetStaticFieldID(build_class, field_name, field_signature);
+  if (strcmp(field_signature, "I") == 0) {
+    env->SetStaticIntField(build_class, fieldId, jint(strtol(new_value.c_str(), nullptr, 0)));
+  } else if (strcmp(field_signature, "Ljava/lang/String;") == 0) {
+    jstring string_val = env->NewStringUTF(new_value.c_str());
+    env->SetStaticObjectField(build_class, fieldId, string_val);
+  } else if (strcmp(field_signature, "[Ljava/lang/String;") == 0) {
+    auto stream = std::stringstream(new_value);
+    std::vector<std::string> items;
+    std::string segment;
+    while (std::getline(stream, segment, ',')) {
+      items.push_back(segment);
+    }
+    jclass string_class = env->FindClass("java/lang/String");
+    jobjectArray string_arr = env->NewObjectArray(items.size(), string_class, nullptr);
+    for (size_t i = 0; i < items.size(); i++) {
+      jstring string_arr_val = env->NewStringUTF(items.at(i).c_str());
+      env->SetObjectArrayElement(string_arr, i, string_arr_val);
+    }
+    env->SetStaticObjectField(build_class, fieldId, string_arr);
+  } else if (strcmp(field_signature, "J") == 0) {
+    env->SetStaticLongField(build_class, fieldId, jlong(strtoll(new_value.c_str(), nullptr, 0)));
+  }
+}
+
+static void ReloadBuildJavaConstants(JNIEnv* env) {
+  jclass build_cls = env->FindClass("android/os/Build");
+  size_t arr_size = sizeof(build_constants) / sizeof(build_constants[0]);
+  for (int i = 0; i < arr_size; i++) {
+    const char* field_name = build_constants[i].first;
+    const char* sysprop_name = build_constants[i].second;
+    ReloadBuildJavaConstant(env, build_cls, field_name, "Ljava/lang/String;", sysprop_name);
+  }
+  jclass build_version_cls = env->FindClass("android/os/Build$VERSION");
+  arr_size = sizeof(build_version_constants) / sizeof(build_version_constants[0]);
+  for (int i = 0; i < arr_size; i++) {
+    const char* field_name = build_version_constants[i].first;
+    const char* sysprop_name = build_version_constants[i].second;
+    ReloadBuildJavaConstant(env, build_version_cls, field_name, "Ljava/lang/String;", sysprop_name);
+  }
+
+  // Reload the public String[] constants
+  ReloadBuildJavaConstant(env, build_cls, "SUPPORTED_ABIS", "[Ljava/lang/String;",
+                          "ro.product.cpu.abilist");
+  ReloadBuildJavaConstant(env, build_cls, "SUPPORTED_32_BIT_ABIS", "[Ljava/lang/String;",
+                          "ro.product.cpu.abilist32");
+  ReloadBuildJavaConstant(env, build_cls, "SUPPORTED_64_BIT_ABIS", "[Ljava/lang/String;",
+                          "ro.product.cpu.abilist64");
+  ReloadBuildJavaConstant(env, build_version_cls, "ALL_CODENAMES", "[Ljava/lang/String;",
+                          "ro.build.version.all_codenames");
+
+  // Reload the public int/long constants
+  ReloadBuildJavaConstant(env, build_cls, "TIME", "J", "ro.build.date.utc");
+  ReloadBuildJavaConstant(env, build_version_cls, "SDK_INT", "I", "ro.build.version.sdk");
+  ReloadBuildJavaConstant(env, build_version_cls, "PREVIEW_SDK_INT", "I",
+                          "ro.build.version.preview_sdk");
+
+  // Re-derive the fingerprint
+  jmethodID derive_fingerprint =
+          env->GetStaticMethodID(build_cls, "deriveFingerprint", "()Ljava/lang/String;");
+  auto new_fingerprint = (jstring)(env->CallStaticObjectMethod(build_cls, derive_fingerprint));
+  jfieldID fieldId = env->GetStaticFieldID(build_cls, "FINGERPRINT", "Ljava/lang/String;");
+  env->SetStaticObjectField(build_cls, fieldId, new_fingerprint);
+}
+
+static void BindMountSyspropOverride(fail_fn_t fail_fn, JNIEnv* env) {
+  std::string source = "/dev/__properties__/appcompat_override";
+  std::string target = "/dev/__properties__";
+  if (access(source.c_str(), F_OK) != 0) {
+    fail_fn(CREATE_ERROR("Error accessing %s: %s", source.c_str(), strerror(errno)));
+  }
+  if (access(target.c_str(), F_OK) != 0) {
+    fail_fn(CREATE_ERROR("Error accessing %s: %s", target.c_str(), strerror(errno)));
+  }
+  BindMount(source, target, fail_fn);
+  // Reload the system properties file, to ensure new values are read into memory
+  __system_properties_zygote_reload();
+  // android.os.Build constants are pulled from system properties, so they must be reloaded, too
+  ReloadBuildJavaConstants(env);
+}
+
 static void BindMountStorageToLowerFs(const userid_t user_id, const uid_t uid,
     const char* dir_name, const char* package, fail_fn_t fail_fn) {
     bool hasSdcardFs = IsSdcardfsUsed();
@@ -1754,7 +1881,7 @@
                              jstring managed_instruction_set, jstring managed_app_data_dir,
                              bool is_top_app, jobjectArray pkg_data_info_list,
                              jobjectArray allowlisted_data_info_list, bool mount_data_dirs,
-                             bool mount_storage_dirs) {
+                             bool mount_storage_dirs, bool mount_sysprop_overrides) {
     const char* process_name = is_system_server ? "system_server" : "zygote";
     auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
     auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1807,6 +1934,10 @@
                              fail_fn);
     }
 
+    if (mount_sysprop_overrides) {
+        BindMountSyspropOverride(fail_fn, env);
+    }
+
     // If this zygote isn't root, it won't be able to create a process group,
     // since the directory is owned by root.
     if (!is_system_server && getuid() == 0) {
@@ -2360,7 +2491,7 @@
         jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
         jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
         jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list,
-        jboolean mount_data_dirs, jboolean mount_storage_dirs) {
+        jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
     if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -2403,7 +2534,7 @@
                          mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
                          instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
                          allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
-                         mount_storage_dirs == JNI_TRUE);
+                         mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
     }
     return pid;
 }
@@ -2439,7 +2570,7 @@
                        effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
                        false, nullptr, nullptr, /* is_top_app= */ false,
                        /* pkg_data_info_list */ nullptr,
-                       /* allowlisted_data_info_list */ nullptr, false, false);
+                       /* allowlisted_data_info_list */ nullptr, false, false, false);
   } else if (pid > 0) {
       // The zygote process checks whether the child process has died or not.
       ALOGI("System server process %d has been created", pid);
@@ -2591,14 +2722,14 @@
         jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
         jboolean is_top_app, jobjectArray pkg_data_info_list,
         jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs,
-        jboolean mount_storage_dirs) {
+        jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
     SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
                      mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
                      instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
                      allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
-                     mount_storage_dirs == JNI_TRUE);
+                     mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
 }
 
 /**
@@ -2876,7 +3007,7 @@
 static const JNINativeMethod gMethods[] = {
         {"nativeForkAndSpecialize",
          "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/"
-         "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I",
+         "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)I",
          (void*)com_android_internal_os_Zygote_nativeForkAndSpecialize},
         {"nativeForkSystemServer", "(II[II[[IJJ)I",
          (void*)com_android_internal_os_Zygote_nativeForkSystemServer},
@@ -2892,7 +3023,7 @@
          (void*)com_android_internal_os_Zygote_nativeAddUsapTableEntry},
         {"nativeSpecializeAppProcess",
          "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/"
-         "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
+         "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)V",
          (void*)com_android_internal_os_Zygote_nativeSpecializeAppProcess},
         {"nativeInitNativeState", "(Z)V",
          (void*)com_android_internal_os_Zygote_nativeInitNativeState},
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 614caffe..0f9c64e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2414,6 +2414,18 @@
                 allowlistedAppDataInfoMap = null;
             }
 
+            boolean bindOverrideSysprops = false;
+            String[] syspropOverridePkgNames = DeviceConfig.getString(
+                    DeviceConfig.NAMESPACE_APP_COMPAT,
+                            "appcompat_sysprop_override_pkgs", "").split(",");
+            String[] pkgs = app.getPackageList();
+            for (int i = 0; i < pkgs.length; i++) {
+                if (ArrayUtils.contains(syspropOverridePkgNames, pkgs[i])) {
+                    bindOverrideSysprops = true;
+                    break;
+                }
+            }
+
             AppStateTracker ast = mService.mServices.mAppStateTracker;
             if (ast != null) {
                 final boolean inBgRestricted = ast.isAppBackgroundRestricted(
@@ -2436,6 +2448,7 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
                         app.getDisabledCompatChanges(),
+                        bindOverrideSysprops,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
             } else if (hostingRecord.usesAppZygote()) {
                 final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
@@ -2447,7 +2460,7 @@
                         app.info.dataDir, null, app.info.packageName,
                         /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
                         app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
-                        false, false,
+                        false, false, bindOverrideSysprops,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
             } else {
                 regularZygote = true;
@@ -2457,6 +2470,7 @@
                         app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
                         isTopApp, app.getDisabledCompatChanges(), pkgDataInfoMap,
                         allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
+                        bindOverrideSysprops,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
                 // By now the process group should have been created by zygote.
                 app.mProcessGroupCreated = true;