arc: Add native_bridge_64bit_support_experiment

Add an experiment flag to enable 64-bit native bridge for ARC on
systems that have 64-bit native bridge support available but not
enabled.

Note that due to the nature of the timing of starting ARC mini-
container - which can be affected by the properties controlled by
this experiment - and user login, the experiment is treated as a one
way off->on transition for all users on a given device.

The experiment flag will have no effect on systems that do not have
64-bit native support available, nor will it affect systems that
have 64-bit native support enabled generally.

Bug: b:62095998
Change-Id: If2a5dc96ba36f8aba31405122a6de20764af30b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2252956
Commit-Queue: Josh Horwich <[email protected]>
Reviewed-by: Yusuke Sato <[email protected]>
Cr-Commit-Position: refs/heads/master@{#786022}
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 2b0459bf..cc6920f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3226,6 +3226,11 @@
     {"arc-native-bridge-toggle", flag_descriptions::kArcNativeBridgeToggleName,
      flag_descriptions::kArcNativeBridgeToggleDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(arc::kNativeBridgeToggleFeature)},
+    {"arc-native-bridge-64bit-support-experiment",
+     flag_descriptions::kArcNativeBridge64BitSupportExperimentName,
+     flag_descriptions::kArcNativeBridge64BitSupportExperimentDescription,
+     kOsCrOS,
+     FEATURE_VALUE_TYPE(arc::kNativeBridge64BitSupportExperimentFeature)},
     {"arc-usb-host", flag_descriptions::kArcUsbHostName,
      flag_descriptions::kArcUsbHostDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(arc::kUsbHostFeature)},
diff --git a/chrome/browser/chromeos/arc/session/arc_session_manager.cc b/chrome/browser/chromeos/arc/session/arc_session_manager.cc
index 908926b..906f2bb 100644
--- a/chrome/browser/chromeos/arc/session/arc_session_manager.cc
+++ b/chrome/browser/chromeos/arc/session/arc_session_manager.cc
@@ -22,6 +22,7 @@
 #include "base/task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/arc/arc_migration_guide_notification.h"
 #include "chrome/browser/chromeos/arc/arc_optin_uma.h"
 #include "chrome/browser/chromeos/arc/arc_support_host.h"
@@ -214,8 +215,10 @@
 
 bool ExpandPropertyFilesInternal(const base::FilePath& source_path,
                                  const base::FilePath& dest_path,
-                                 bool single_file) {
-  if (!arc::ExpandPropertyFiles(source_path, dest_path, single_file))
+                                 bool single_file,
+                                 bool add_native_bridge_64bit_support) {
+  if (!arc::ExpandPropertyFiles(source_path, dest_path, single_file,
+                                add_native_bridge_64bit_support))
     return false;
   if (!arc::IsArcVmEnabled())
     return true;
@@ -1333,12 +1336,29 @@
   // For ARCVM, generate <dest_path>/{combined.prop,fstab}. For ARC, generate
   // <dest_path>/{default,build,vendor_build}.prop.
   const bool is_arcvm = arc::IsArcVmEnabled();
+  bool add_native_bridge_64bit_support = false;
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          chromeos::switches::kArcEnableNativeBridge64BitSupportExperiment)) {
+    PrefService* local_pref_service = g_browser_process->local_state();
+    if (base::FeatureList::IsEnabled(
+            arc::kNativeBridge64BitSupportExperimentFeature)) {
+      // Note that we treat this experiment as a one-way off->on switch, across
+      // all users of the device, as the lifetime of ARC mini-container and user
+      // sessions are different in different scenarios, and removing the
+      // experiment after it has been in effect for a user's ARC instance can
+      // lead to unexpected, and unsupported, results.
+      local_pref_service->SetBoolean(
+          prefs::kNativeBridge64BitSupportExperimentEnabled, true);
+    }
+    add_native_bridge_64bit_support = local_pref_service->GetBoolean(
+        prefs::kNativeBridge64BitSupportExperimentEnabled);
+  }
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
       base::BindOnce(&ExpandPropertyFilesInternal, property_files_source_dir_,
                      is_arcvm ? property_files_dest_dir_.Append("combined.prop")
                               : property_files_dest_dir_,
-                     /*single_file=*/is_arcvm),
+                     /*single_file=*/is_arcvm, add_native_bridge_64bit_support),
       base::BindOnce(&ArcSessionManager::OnExpandPropertyFiles,
                      weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 1cce9b4..ae371b7 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -184,6 +184,13 @@
     "expiry_milestone": 76
   },
   {
+    "name": "arc-native-bridge-64bit-support-experiment",
+    "owners": [ "jhorwich" ],
+    // Used on ChromeOS to experimentally enable 64-bit ARC native-bridge
+    // support before promoting it to enabled on a specific device class.
+    "expiry_milestone": 93
+  },
+  {
     "name": "arc-native-bridge-toggle",
     "owners": [ "[email protected]" ],
     // Used on ChromeOS to compare and debug different ARC native-bridge
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 262554a55..4eec203 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3325,6 +3325,11 @@
 const char kArcNativeBridgeToggleDescription[] =
     "Toggle between native bridge implementations for ARC.";
 
+const char kArcNativeBridge64BitSupportExperimentName[] =
+    "Enable experimental 64-bit native bridge support for ARC";
+const char kArcNativeBridge64BitSupportExperimentDescription[] =
+    "Enable experimental 64-bit native bridge support for ARC where available.";
+
 const char kArcUsbHostName[] = "Enable ARC USB host integration";
 const char kArcUsbHostDescription[] =
     "Allow Android apps to use USB host feature on ChromeOS devices.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index bfe4dff..6616f270 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1921,6 +1921,9 @@
 extern const char kArcNativeBridgeToggleName[];
 extern const char kArcNativeBridgeToggleDescription[];
 
+extern const char kArcNativeBridge64BitSupportExperimentName[];
+extern const char kArcNativeBridge64BitSupportExperimentDescription[];
+
 extern const char kArcUsbHostName[];
 extern const char kArcUsbHostDescription[];
 
diff --git a/chromeos/constants/chromeos_switches.cc b/chromeos/constants/chromeos_switches.cc
index 62ecd83..25a486b23 100644
--- a/chromeos/constants/chromeos_switches.cc
+++ b/chromeos/constants/chromeos_switches.cc
@@ -83,6 +83,12 @@
 // in autotests to resolve racy conditions.
 const char kArcDisableAppSync[] = "arc-disable-app-sync";
 
+// Flag to enables an experiment to allow users to turn on 64-bit support in
+// native bridge on systems that have such support available but not yet enabled
+// by default.
+const char kArcEnableNativeBridge64BitSupportExperiment[] =
+    "arc-enable-native-bridge-64bit-support-experiment";
+
 // Used in autotest to disable GMS-core caches which is on by default.
 const char kArcDisableGmsCoreCache[] = "arc-disable-gms-core-cache";
 
diff --git a/chromeos/constants/chromeos_switches.h b/chromeos/constants/chromeos_switches.h
index 3b1b27fc..4da209e2 100644
--- a/chromeos/constants/chromeos_switches.h
+++ b/chromeos/constants/chromeos_switches.h
@@ -45,6 +45,8 @@
 extern const char kArcDisableSystemDefaultApps[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const char kArcDisablePlayAutoInstall[];
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const char kArcEnableNativeBridge64BitSupportExperiment[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kArcForceCacheAppIcons[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kArcForceShowOptInUi[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kArcPackagesCacheMode[];
diff --git a/components/arc/arc_features.cc b/components/arc/arc_features.cc
index d1bbe371..ad16607f 100644
--- a/components/arc/arc_features.cc
+++ b/components/arc/arc_features.cc
@@ -57,6 +57,11 @@
 const base::Feature kFilePickerExperimentFeature{
     "ArcFilePickerExperiment", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Controls experimental 64-bit native bridge support for ARC on boards that
+// have 64-bit native bridge support available but not yet enabled.
+const base::Feature kNativeBridge64BitSupportExperimentFeature{
+    "ArcNativeBridge64BitSupportExperiment", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Toggles between native bridge implementations for ARC.
 // Note, that we keep the original feature name to preserve
 // corresponding metrics.
diff --git a/components/arc/arc_features.h b/components/arc/arc_features.h
index b03563cb..4416785 100644
--- a/components/arc/arc_features.h
+++ b/components/arc/arc_features.h
@@ -23,6 +23,7 @@
 extern const base::Feature kEnableSecondaryAccountsForChildExperiment;
 extern const base::Feature kEnableUnifiedAudioFocusFeature;
 extern const base::Feature kFilePickerExperimentFeature;
+extern const base::Feature kNativeBridge64BitSupportExperimentFeature;
 extern const base::Feature kNativeBridgeToggleFeature;
 extern const base::Feature kPictureInPictureFeature;
 extern const base::Feature kSmartTextSelectionFeature;
diff --git a/components/arc/arc_prefs.cc b/components/arc/arc_prefs.cc
index 8cba86c..dfa9701a6 100644
--- a/components/arc/arc_prefs.cc
+++ b/components/arc/arc_prefs.cc
@@ -115,6 +115,13 @@
 
 // ======== LOCAL STATE PREFS ========
 
+// A boolean preference that indicates whether this device has run with the
+// native bridge 64 bit support experiment enabled. Persisting value in local
+// state, rather than in a user profile, is required as it needs to be read
+// whenever ARC mini-container is started.
+const char kNativeBridge64BitSupportExperimentEnabled[] =
+    "arc.native_bridge_64bit_support_experiment";
+
 // A dictionary preference that keeps track of stability metric values, which is
 // maintained by StabilityMetricsManager. Persisting values in local state is
 // required to include these metrics in the initial stability log in case of a
@@ -122,6 +129,8 @@
 const char kStabilityMetrics[] = "arc.metrics.stability";
 
 void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(kNativeBridge64BitSupportExperimentEnabled,
+                                false);
   registry->RegisterDictionaryPref(kStabilityMetrics);
 }
 
diff --git a/components/arc/arc_prefs.h b/components/arc/arc_prefs.h
index 010604a..0734094 100644
--- a/components/arc/arc_prefs.h
+++ b/components/arc/arc_prefs.h
@@ -45,6 +45,7 @@
 ARC_EXPORT extern const char kEngagementPrefsPrefix[];
 
 // Local state prefs in lexicographical order.
+ARC_EXPORT extern const char kNativeBridge64BitSupportExperimentEnabled[];
 ARC_EXPORT extern const char kStabilityMetrics[];
 
 void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
diff --git a/components/arc/session/arc_property_util.cc b/components/arc/session/arc_property_util.cc
index 0a4ca1a4..e4066f5 100644
--- a/components/arc/session/arc_property_util.cc
+++ b/components/arc/session/arc_property_util.cc
@@ -5,6 +5,7 @@
 #include "components/arc/session/arc_property_util.h"
 
 #include <algorithm>
+#include <tuple>
 #include <vector>
 
 #include "base/command_line.h"
@@ -38,6 +39,16 @@
 // separated list of regions to include in the OEM key property.
 constexpr char kPAIRegionsPropertyName[] = "pai-regions";
 
+// Properties related to dynamically adding native bridge 64 bit support.
+constexpr char kAbilistPropertyPrefix[] = "ro.product.cpu.abilist=";
+constexpr char kAbilistPropertyExpected[] = "x86_64,x86,armeabi-v7a,armeabi";
+constexpr char kAbilistPropertyReplacement[] =
+    "x86_64,x86,arm64-v8a,armeabi-v7a,armeabi";
+constexpr char kAbilist64PropertyPrefix[] = "ro.product.cpu.abilist64=";
+constexpr char kAbilist64PropertyExpected[] = "x86_64";
+constexpr char kAbilist64PropertyReplacement[] = "x86_64,arm64-v8a";
+constexpr char kDalvikVmIsaArm64[] = "ro.dalvik.vm.isa.arm64=x86_64";
+
 // Maximum length of an Android property value.
 constexpr int kAndroidMaxPropertyLength = 91;
 
@@ -146,7 +157,8 @@
 
 bool ExpandPropertyContents(const std::string& content,
                             brillo::CrosConfigInterface* config,
-                            std::string* expanded_content) {
+                            std::string* expanded_content,
+                            bool add_native_bridge_64bit_support) {
   const std::vector<std::string> lines = base::SplitString(
       content, "\n", base::WhitespaceHandling::KEEP_WHITESPACE,
       base::SplitResult::SPLIT_WANT_ALL);
@@ -192,12 +204,38 @@
         expanded += line.substr(prev_match);
       line = expanded;
     } while (inserted);
+    if (add_native_bridge_64bit_support) {
+      // Special-case ro.product.cpu.abilist / ro.product.cpu.abilist64 to add
+      // ARM64.
+      std::string value;
+      if (FindProperty(kAbilistPropertyPrefix, &value, line)) {
+        if (value == kAbilistPropertyExpected) {
+          line = std::string(kAbilistPropertyPrefix) +
+                 std::string(kAbilistPropertyReplacement);
+        } else {
+          LOG(ERROR) << "Found unexpected value for " << kAbilistPropertyPrefix
+                     << ", value " << value;
+          return false;
+        }
+      }
+      if (FindProperty(kAbilist64PropertyPrefix, &value, line)) {
+        if (value == kAbilist64PropertyExpected) {
+          line = std::string(kAbilist64PropertyPrefix) +
+                 std::string(kAbilist64PropertyReplacement);
+        } else {
+          LOG(ERROR) << "Found unexpected value for "
+                     << kAbilist64PropertyPrefix << ", value " << value;
+          return false;
+        }
+      }
+    }
 
     std::string truncated;
     if (!TruncateAndroidProperty(line, &truncated)) {
       LOG(ERROR) << "Unable to truncate property: " << line;
       return false;
     }
+
     new_properties += truncated + "\n";
 
     // Special-case ro.product.board to compute ro.oem.key1 at runtime, as it
@@ -210,6 +248,11 @@
     }
   }
 
+  if (add_native_bridge_64bit_support) {
+    // Special-case to add ro.dalvik.vm.isa.arm64.
+    new_properties += std::string(kDalvikVmIsaArm64) + "\n";
+  }
+
   *expanded_content = new_properties;
   return true;
 }
@@ -217,14 +260,16 @@
 bool ExpandPropertyFile(const base::FilePath& input,
                         const base::FilePath& output,
                         CrosConfig* config,
-                        bool append) {
+                        bool append,
+                        bool add_native_bridge_64bit_support) {
   std::string content;
   std::string expanded;
   if (!base::ReadFileToString(input, &content)) {
     PLOG(ERROR) << "Failed to read " << input;
     return false;
   }
-  if (!ExpandPropertyContents(content, config, &expanded))
+  if (!ExpandPropertyContents(content, config, &expanded,
+                              add_native_bridge_64bit_support))
     return false;
   if (append && base::PathExists(output)) {
     if (!base::AppendToFile(output, expanded.data(), expanded.size())) {
@@ -284,7 +329,8 @@
 bool ExpandPropertyContentsForTesting(const std::string& content,
                                       brillo::CrosConfigInterface* config,
                                       std::string* expanded_content) {
-  return ExpandPropertyContents(content, config, expanded_content);
+  return ExpandPropertyContents(content, config, expanded_content,
+                                /*add_native_bridge_64bit_support=*/false);
 }
 
 bool TruncateAndroidPropertyForTesting(const std::string& line,
@@ -295,30 +341,35 @@
 bool ExpandPropertyFileForTesting(const base::FilePath& input,
                                   const base::FilePath& output,
                                   CrosConfig* config) {
-  return ExpandPropertyFile(input, output, config, /*append=*/false);
+  return ExpandPropertyFile(input, output, config, /*append=*/false,
+                            /*add_native_bridge_64bit_support=*/false);
 }
 
 bool ExpandPropertyFiles(const base::FilePath& source_path,
                          const base::FilePath& dest_path,
-                         bool single_file) {
+                         bool single_file,
+                         bool add_native_bridge_64bit_support) {
   CrosConfig config;
   if (single_file)
     base::DeleteFile(dest_path, /*recursive=*/false);
 
   // default.prop may not exist. Silently skip it if not found.
-  for (const auto& pair : {std::pair<const char*, bool>{"default.prop", true},
-                           {"build.prop", false},
-                           {"vendor_build.prop", false}}) {
-    const char* file = pair.first;
-    const bool is_optional = pair.second;
+  for (const auto& tuple :
+       {std::tuple<const char*, bool, bool>{"default.prop", true, false},
+        {"build.prop", false, true},
+        {"vendor_build.prop", false, false}}) {
+    const char* file = std::get<0>(tuple);
+    const bool is_optional = std::get<1>(tuple);
+    const bool add_native_bridge_properties =
+        std::get<2>(tuple) && add_native_bridge_64bit_support;
 
     if (is_optional && !base::PathExists(source_path.Append(file)))
       continue;
 
-    if (!ExpandPropertyFile(source_path.Append(file),
-                            single_file ? dest_path : dest_path.Append(file),
-                            &config,
-                            /*append=*/single_file)) {
+    if (!ExpandPropertyFile(
+            source_path.Append(file),
+            single_file ? dest_path : dest_path.Append(file), &config,
+            /*append=*/single_file, add_native_bridge_properties)) {
       LOG(ERROR) << "Failed to expand " << source_path.Append(file);
       return false;
     }
diff --git a/components/arc/session/arc_property_util.h b/components/arc/session/arc_property_util.h
index 4748e56..8073ee45 100644
--- a/components/arc/session/arc_property_util.h
+++ b/components/arc/session/arc_property_util.h
@@ -61,9 +61,12 @@
 // |source_path|. Expanded files are written in |dest_path|. Returns true on
 // success. When |single_file| is true, only one file (|dest_path| itself) is
 // written. All expanded properties are included in the single file.
+// When |add_native_bridge_64_bit_support| is true, add / modify some properties
+// related to supported CPU ABIs.
 bool ExpandPropertyFiles(const base::FilePath& source_path,
                          const base::FilePath& dest_path,
-                         bool single_file);
+                         bool single_file,
+                         bool add_native_bridge_64bit_support);
 
 }  // namespace arc
 
diff --git a/components/arc/session/arc_property_util_unittest.cc b/components/arc/session/arc_property_util_unittest.cc
index 1583bc1..7f60f7c 100644
--- a/components/arc/session/arc_property_util_unittest.cc
+++ b/components/arc/session/arc_property_util_unittest.cc
@@ -291,33 +291,34 @@
   // Both source and dest are not found.
   EXPECT_FALSE(ExpandPropertyFiles(base::FilePath("/nonexistent1"),
                                    base::FilePath("/nonexistent2"),
-                                   /*single_file=*/false));
+                                   /*single_file=*/false,
+                                   /*add_native_bridge...=*/false));
 
   // Both source and dest exist, but the source directory is empty.
   base::FilePath source_dir;
   ASSERT_TRUE(base::CreateTemporaryDirInDir(GetTempDir(), "test", &source_dir));
   base::FilePath dest_dir;
   ASSERT_TRUE(base::CreateTemporaryDirInDir(GetTempDir(), "test", &dest_dir));
-  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false, false));
 
   // Add default.prop to the source, but not build.prop.
   base::FilePath default_prop = source_dir.Append("default.prop");
   constexpr const char kDefaultProp[] = "ro.foo=bar\n";
   base::WriteFile(default_prop, kDefaultProp, strlen(kDefaultProp));
-  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false, false));
 
   // Add build.prop too. The call should not succeed still.
   base::FilePath build_prop = source_dir.Append("build.prop");
   constexpr const char kBuildProp[] = "ro.baz=boo\n";
   base::WriteFile(build_prop, kBuildProp, strlen(kBuildProp));
-  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false, false));
 
   // Add vendor_build.prop too. Then the call should succeed.
   base::FilePath vendor_build_prop = source_dir.Append("vendor_build.prop");
   constexpr const char kVendorBuildProp[] = "ro.a=b\n";
   base::WriteFile(vendor_build_prop, kVendorBuildProp,
                   strlen(kVendorBuildProp));
-  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false));
+  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false, false));
 
   // Verify all dest files are there.
   EXPECT_TRUE(base::PathExists(dest_dir.Append("default.prop")));
@@ -337,7 +338,7 @@
   EXPECT_EQ(std::string(kVendorBuildProp) + "\n", content);
 
   // Expand it again, verify the previous result is cleared.
-  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false));
+  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false, false));
   EXPECT_TRUE(
       base::ReadFileToString(dest_dir.Append("default.prop"), &content));
   EXPECT_EQ(std::string(kDefaultProp) + "\n", content);
@@ -347,7 +348,7 @@
   // destination path.
   base::DeleteFile(dest_dir.Append("default.prop"), /*recursive=*/false);
 
-  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false));
+  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false, false));
 
   EXPECT_TRUE(base::ReadFileToString(dest_dir.Append("build.prop"), &content));
   EXPECT_EQ(std::string(kBuildProp) + "\n", content);
@@ -356,8 +357,8 @@
   EXPECT_EQ(std::string(kVendorBuildProp) + "\n", content);
 
   // Finally, test the case where source is valid but the dest is not.
-  EXPECT_FALSE(
-      ExpandPropertyFiles(source_dir, base::FilePath("/nonexistent"), false));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, base::FilePath("/nonexistent"),
+                                   false, false));
 }
 
 // Do the same as the previous test, but with |single_file| == true.
@@ -365,7 +366,8 @@
   // Both source and dest are not found.
   EXPECT_FALSE(ExpandPropertyFiles(base::FilePath("/nonexistent1"),
                                    base::FilePath("/nonexistent2"),
-                                   /*single_file=*/true));
+                                   /*single_file=*/true,
+                                   /*add_native_bridge...=*/false));
 
   // Both source and dest exist, but the source directory is empty.
   base::FilePath source_dir;
@@ -374,26 +376,26 @@
   ASSERT_TRUE(
       base::CreateTemporaryDirInDir(GetTempDir(), "test", &dest_prop_file));
   dest_prop_file = dest_prop_file.Append("combined.prop");
-  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_prop_file, true));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_prop_file, true, false));
 
   // Add default.prop to the source, but not build.prop.
   base::FilePath default_prop = source_dir.Append("default.prop");
   constexpr const char kDefaultProp[] = "ro.foo=bar\n";
   base::WriteFile(default_prop, kDefaultProp, strlen(kDefaultProp));
-  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_prop_file, true));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_prop_file, true, false));
 
   // Add build.prop too. The call should not succeed still.
   base::FilePath build_prop = source_dir.Append("build.prop");
   constexpr const char kBuildProp[] = "ro.baz=boo\n";
   base::WriteFile(build_prop, kBuildProp, strlen(kBuildProp));
-  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_prop_file, true));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_prop_file, true, false));
 
   // Add vendor_build.prop too. Then the call should succeed.
   base::FilePath vendor_build_prop = source_dir.Append("vendor_build.prop");
   constexpr const char kVendorBuildProp[] = "ro.a=b\n";
   base::WriteFile(vendor_build_prop, kVendorBuildProp,
                   strlen(kVendorBuildProp));
-  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true));
+  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true, false));
 
   // Verify only one dest file exists.
   EXPECT_FALSE(
@@ -412,7 +414,7 @@
             content);
 
   // Expand it again, verify the previous result is cleared.
-  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true));
+  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true, false));
   EXPECT_TRUE(base::ReadFileToString(dest_prop_file, &content));
   EXPECT_EQ(base::StringPrintf("%s\n%s\n%s\n", kDefaultProp, kBuildProp,
                                kVendorBuildProp),
@@ -422,14 +424,99 @@
   // the other files.
   base::DeleteFile(source_dir.Append("default.prop"),
                    /*recursive=*/false);
-  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true));
+  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true, false));
   EXPECT_TRUE(base::ReadFileToString(dest_prop_file, &content));
   EXPECT_EQ(base::StringPrintf("%s\n%s\n", kBuildProp, kVendorBuildProp),
             content);
 
   // Finally, test the case where source is valid but the dest is not.
-  EXPECT_FALSE(
-      ExpandPropertyFiles(source_dir, base::FilePath("/nonexistent"), true));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, base::FilePath("/nonexistent"),
+                                   true, false));
+}
+
+// Test that ExpandPropertyFiles handles properties related to native bridge
+// 64-bit support properly.
+TEST_F(ArcPropertyUtilTest, TestNativeBridge64Support) {
+  // Set up some properties files.
+  base::FilePath source_dir;
+  ASSERT_TRUE(base::CreateTemporaryDirInDir(GetTempDir(), "test", &source_dir));
+  base::FilePath dest_dir;
+  ASSERT_TRUE(base::CreateTemporaryDirInDir(GetTempDir(), "test", &dest_dir));
+
+  base::FilePath default_prop = source_dir.Append("default.prop");
+  constexpr const char kDefaultProp[] = "ro.foo=bar\n";
+  base::WriteFile(default_prop, kDefaultProp, strlen(kDefaultProp));
+
+  base::FilePath build_prop = source_dir.Append("build.prop");
+  constexpr const char kBuildProp[] =
+      "ro.baz=boo\n"
+      "ro.product.cpu.abilist=x86_64,x86,armeabi-v7a,armeabi\n"
+      "ro.product.cpu.abilist64=x86_64\n";
+  base::WriteFile(build_prop, kBuildProp, strlen(kBuildProp));
+
+  base::FilePath vendor_build_prop = source_dir.Append("vendor_build.prop");
+  constexpr const char kVendorBuildProp[] = "ro.a=b\n";
+  base::WriteFile(vendor_build_prop, kVendorBuildProp,
+                  strlen(kVendorBuildProp));
+
+  // Expand with experiment off, verify properties are untouched.
+  std::string content;
+  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false, false));
+  EXPECT_TRUE(
+      base::ReadFileToString(dest_dir.Append("default.prop"), &content));
+  EXPECT_EQ(std::string(kDefaultProp) + "\n", content);
+  EXPECT_TRUE(base::ReadFileToString(dest_dir.Append("build.prop"), &content));
+  EXPECT_EQ(std::string(kBuildProp) + "\n", content);
+  EXPECT_TRUE(
+      base::ReadFileToString(dest_dir.Append("vendor_build.prop"), &content));
+  EXPECT_EQ(std::string(kVendorBuildProp) + "\n", content);
+
+  // Expand with experiment on, verify properties are added / modified in
+  // build.prop but not other files.
+  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false, true));
+  EXPECT_TRUE(
+      base::ReadFileToString(dest_dir.Append("default.prop"), &content));
+  EXPECT_EQ(std::string(kDefaultProp) + "\n", content);
+  EXPECT_TRUE(base::ReadFileToString(dest_dir.Append("build.prop"), &content));
+  constexpr const char kBuildPropModified[] =
+      "ro.baz=boo\n"
+      "ro.product.cpu.abilist=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi\n"
+      "ro.product.cpu.abilist64=x86_64,arm64-v8a\n\n"
+      "ro.dalvik.vm.isa.arm64=x86_64";
+  EXPECT_EQ(std::string(kBuildPropModified) + "\n", content);
+  EXPECT_TRUE(
+      base::ReadFileToString(dest_dir.Append("vendor_build.prop"), &content));
+  EXPECT_EQ(std::string(kVendorBuildProp) + "\n", content);
+
+  // Expand to a single file with experiment on, verify properties are added /
+  // modified as expected.
+  base::FilePath dest_prop_file;
+  ASSERT_TRUE(
+      base::CreateTemporaryDirInDir(GetTempDir(), "test", &dest_prop_file));
+  dest_prop_file = dest_prop_file.Append("combined.prop");
+  EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true, true));
+
+  // Verify the contents.
+  EXPECT_TRUE(base::ReadFileToString(dest_prop_file, &content));
+  EXPECT_EQ(base::StringPrintf("%s\n%s\n%s\n", kDefaultProp, kBuildPropModified,
+                               kVendorBuildProp),
+            content);
+
+  // Verify that unexpected property values generate an error.
+  constexpr const char kBuildPropUnexpected[] =
+      "ro.baz=boo\n"
+      "ro.product.cpu.abilist=x86_64,armeabi-v7a,armeabi,unexpected-abi\n"
+      "ro.product.cpu.abilist64=x86_64\n";
+  base::WriteFile(build_prop, kBuildPropUnexpected,
+                  strlen(kBuildPropUnexpected));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false, true));
+  constexpr const char kBuildPropUnexpected2[] =
+      "ro.baz=boo\n"
+      "ro.product.cpu.abilist=x86_64,x86,armeabi-v7a,armeabi\n"
+      "ro.product.cpu.abilist64=x86_64,unexpected-abi_64\n";
+  base::WriteFile(build_prop, kBuildPropUnexpected2,
+                  strlen(kBuildPropUnexpected2));
+  EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false, true));
 }
 
 }  // namespace
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 86bd084..15027ea 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -39863,6 +39863,8 @@
   <int value="-1670137340"
       label="OptimizeLoadingIPCForSmallResources:disabled"/>
   <int value="-1669486359" label="ImportantSitesInCBD:enabled"/>
+  <int value="-1665720309"
+      label="ArcNativeBridge64BitSupportExperiment:disabled"/>
   <int value="-1664795930" label="StorageAccessAPI:disabled"/>
   <int value="-1663410466" label="top-document-isolation"/>
   <int value="-1663125441" label="OptInImeMenu:enabled"/>
@@ -40379,6 +40381,8 @@
   <int value="-1153892430"
       label="AlignFontDisplayAutoTimeoutWithLCPGoal:enabled"/>
   <int value="-1151766565" label="enable-fullscreen-tab-detaching"/>
+  <int value="-1150881704"
+      label="ArcNativeBridge64BitSupportExperiment:enabled"/>
   <int value="-1145905507" label="SendTabToSelfWhenSignedIn:disabled"/>
   <int value="-1145702446" label="ChromeHomeInactivitySheetExpansion:enabled"/>
   <int value="-1145246849" label="ThirdPartyDoodles:enabled"/>