Add a util class for getting system properties.

System properties affect artd's behavior on app compilation. This class
makes system properties mockable so that artd can be unit-tested. It
also provides fallback lookup support, which is useful for artd.

This class is added to libarttools because it can potentially be shared
with other code, such as odrefresh.

Bug: 229268202
Test: m test-art-host-gtest-art_libarttools_tests
Ignore-AOSP-First: ART Services
Change-Id: Ibb9322db8124ca6d8a54a3ee65ad9a4ffc730845
diff --git a/artd/artd.cc b/artd/artd.cc
index 392f587..8f520e6 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -30,7 +30,6 @@
 #include "aidl/com/android/server/art/BnArtd.h"
 #include "android-base/errors.h"
 #include "android-base/logging.h"
-#include "android-base/properties.h"
 #include "android-base/result.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
@@ -52,7 +51,6 @@
 using ::aidl::com::android::server::art::ArtifactsPath;
 using ::aidl::com::android::server::art::GetOptimizationStatusResult;
 using ::android::base::Error;
-using ::android::base::GetBoolProperty;
 using ::android::base::Result;
 using ::android::base::Split;
 using ::android::base::StringPrintf;
@@ -60,9 +58,6 @@
 
 constexpr const char* kServiceName = "artd";
 
-constexpr const char* kPhenotypeFlagPrefix = "persist.device_config.runtime_native_boot.";
-constexpr const char* kDalvikVmFlagPrefix = "dalvik.vm.";
-
 Result<std::vector<std::string>> GetBootClassPath() {
   const char* env_value = getenv("BOOTCLASSPATH");
   if (env_value == nullptr || strlen(env_value) == 0) {
@@ -82,22 +77,6 @@
   return Split(location_str, ":");
 }
 
-bool UseJitZygote() {
-  bool profile_boot_class_path_phenotype =
-      GetBoolProperty(std::string(kPhenotypeFlagPrefix) + "profilebootclasspath",
-                      /*default_value=*/false);
-
-  bool profile_boot_class_path =
-      GetBoolProperty(std::string(kDalvikVmFlagPrefix) + "profilebootclasspath",
-                      /*default_value=*/profile_boot_class_path_phenotype);
-
-  return profile_boot_class_path;
-}
-
-bool DenyArtApexDataFiles() {
-  return !GetBoolProperty("odsign.verification.success", /*default_value=*/false);
-}
-
 // Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an
 // error occurs.
 int64_t GetSizeAndDeleteFile(const std::string& path) {
@@ -233,5 +212,15 @@
 
 bool Artd::HasRuntimeOptionsCache() const { return !cached_boot_image_locations_.empty(); }
 
+bool Artd::UseJitZygote() const {
+  return props_->GetBool("dalvik.vm.profilebootclasspath",
+                         "persist.device_config.runtime_native_boot.profilebootclasspath",
+                         /*default_value=*/false);
+}
+
+bool Artd::DenyArtApexDataFiles() const {
+  return !props_->GetBool("odsign.verification.success", /*default_value=*/false);
+}
+
 }  // namespace artd
 }  // namespace art
diff --git a/artd/artd.h b/artd/artd.h
index 2a05267..cb7a12e 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -24,12 +24,17 @@
 #include "android-base/result.h"
 #include "android/binder_auto_utils.h"
 #include "oat_file_assistant.h"
+#include "tools/system_properties.h"
 
 namespace art {
 namespace artd {
 
 class Artd : public aidl::com::android::server::art::BnArtd {
  public:
+  explicit Artd(std::unique_ptr<art::tools::SystemProperties> props =
+                    std::make_unique<art::tools::SystemProperties>())
+      : props_(std::move(props)) {}
+
   ndk::ScopedAStatus isAlive(bool* _aidl_return) override;
 
   ndk::ScopedAStatus deleteArtifacts(
@@ -51,10 +56,15 @@
 
   bool HasRuntimeOptionsCache() const;
 
+  bool UseJitZygote() const;
+
+  bool DenyArtApexDataFiles() const;
+
   std::vector<std::string> cached_boot_image_locations_;
   std::vector<std::string> cached_boot_class_path_;
   std::string cached_apex_versions_;
   bool cached_deny_art_apex_data_files_;
+  std::unique_ptr<art::tools::SystemProperties> props_;
 };
 
 }  // namespace artd
diff --git a/libarttools/Android.bp b/libarttools/Android.bp
index 3df40a5..ff3f4d1 100644
--- a/libarttools/Android.bp
+++ b/libarttools/Android.bp
@@ -49,12 +49,16 @@
 art_cc_defaults {
     name: "art_libarttools_tests_defaults",
     srcs: [
+        "tools/system_properties_test.cc",
         "tools/tools_test.cc",
     ],
     shared_libs: [
         "libbase",
         "libarttools",
     ],
+    static_libs: [
+        "libgmock",
+    ],
 }
 
 // Version of ART gtest `art_libarttools_tests` bundled with the ART APEX on target.
diff --git a/libarttools/tools/system_properties.h b/libarttools/tools/system_properties.h
new file mode 100644
index 0000000..06b7bcb
--- /dev/null
+++ b/libarttools/tools/system_properties.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#ifndef ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
+#define ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
+
+#include <string>
+
+#include "android-base/parsebool.h"
+#include "android-base/properties.h"
+
+namespace art {
+namespace tools {
+
+// A class for getting system properties with fallback lookup support. Different from
+// android::base::GetProperty, this class is mockable.
+class SystemProperties {
+ public:
+  virtual ~SystemProperties() = default;
+
+  // Returns the current value of the system property `key`, or `default_value` if the property
+  // doesn't have a value.
+  std::string Get(const std::string& key, const std::string& default_value) const {
+    std::string value = GetProperty(key);
+    if (!value.empty()) {
+      return value;
+    }
+    return default_value;
+  }
+
+  // Same as above, but allows specifying one or more fallback keys. The last argument is a string
+  // default value that will be used if none of the given keys has a value.
+  //
+  // Usage:
+  //
+  // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return "default":
+  //   Get("key_1", "key_2", "key_3", /*default_value=*/"default")
+  template <typename... Args>
+  std::string Get(const std::string& key, const std::string& fallback_key, Args... args) const {
+    return Get(key, Get(fallback_key, args...));
+  }
+
+  // Returns the current value of the system property `key` with zero or more fallback keys, or an
+  // empty string if none of the given keys has a value.
+  //
+  // Usage:
+  //
+  // Look up for "key_1". If it doesn't have a value, return an empty string:
+  //  GetOrEmpty("key_1")
+  //
+  // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return an empty
+  // string:
+  //  GetOrEmpty("key_1", "key_2", "key_3")
+  template <typename... Args>
+  std::string GetOrEmpty(const std::string& key, Args... fallback_keys) const {
+    return Get(key, fallback_keys..., /*default_value=*/"");
+  }
+
+  // Returns the current value of the boolean system property `key`, or `default_value` if the
+  // property doesn't have a value. See `android::base::ParseBool` for how the value is parsed.
+  bool GetBool(const std::string& key, bool default_value) const {
+    android::base::ParseBoolResult result = android::base::ParseBool(GetProperty(key));
+    if (result != android::base::ParseBoolResult::kError) {
+      return result == android::base::ParseBoolResult::kTrue;
+    }
+    return default_value;
+  }
+
+  // Same as above, but allows specifying one or more fallback keys. The last argument is a bool
+  // default value that will be used if none of the given keys has a value.
+  //
+  // Usage:
+  //
+  // Look up for "key_1", then "key_2", then "key_3". If none of them has a value, return true:
+  //   Get("key_1", "key_2", "key_3", /*default_value=*/true)
+  template <typename... Args>
+  bool GetBool(const std::string& key, const std::string& fallback_key, Args... args) const {
+    return GetBool(key, GetBool(fallback_key, args...));
+  }
+
+ protected:
+  // The single source of truth of system properties. Can be mocked in unit tests.
+  virtual std::string GetProperty(const std::string& key) const {
+    return android::base::GetProperty(key, /*default_value=*/"");
+  }
+};
+
+}  // namespace tools
+}  // namespace art
+
+#endif  // ART_LIBARTTOOLS_TOOLS_SYSTEM_PROPERTIES_H_
diff --git a/libarttools/tools/system_properties_test.cc b/libarttools/tools/system_properties_test.cc
new file mode 100644
index 0000000..80300f0
--- /dev/null
+++ b/libarttools/tools/system_properties_test.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 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 "system_properties.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace tools {
+namespace {
+
+using ::testing::Return;
+
+class MockSystemProperties : public SystemProperties {
+ public:
+  MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override));
+};
+
+class SystemPropertiesTest : public testing::Test {
+ protected:
+  MockSystemProperties system_properties_;
+};
+
+TEST_F(SystemPropertiesTest, Get) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("value_1"));
+  EXPECT_EQ(system_properties_.Get("key_1", /*default_value=*/"default"), "value_1");
+}
+
+TEST_F(SystemPropertiesTest, GetWithFallback) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("value_2"));
+  EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("value_3"));
+  EXPECT_EQ(system_properties_.Get("key_1", "key_2", "key_3", /*default_value=*/"default"),
+            "value_2");
+}
+
+TEST_F(SystemPropertiesTest, GetDefault) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_EQ(system_properties_.Get("key_1", /*default_value=*/"default"), "default");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmpty) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("value_1"));
+  EXPECT_EQ(system_properties_.GetOrEmpty("key_1"), "value_1");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmptyWithFallback) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("value_2"));
+  EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("value_3"));
+  EXPECT_EQ(system_properties_.GetOrEmpty("key_1", "key_2", "key_3"), "value_2");
+}
+
+TEST_F(SystemPropertiesTest, GetOrEmptyDefault) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_EQ(system_properties_.GetOrEmpty("key_1"), "");
+}
+
+TEST_F(SystemPropertiesTest, GetBoolTrue) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("true"));
+  EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/false), true);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolFalse) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return("false"));
+  EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/true), false);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolWithFallback) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_CALL(system_properties_, GetProperty("key_2")).WillOnce(Return("true"));
+  EXPECT_CALL(system_properties_, GetProperty("key_3")).WillOnce(Return("false"));
+  EXPECT_EQ(system_properties_.GetBool("key_1", "key_2", "key_3", /*default_value=*/false), true);
+}
+
+TEST_F(SystemPropertiesTest, GetBoolDefault) {
+  EXPECT_CALL(system_properties_, GetProperty("key_1")).WillOnce(Return(""));
+  EXPECT_EQ(system_properties_.GetBool("key_1", /*default_value=*/true), true);
+}
+
+}  // namespace
+}  // namespace tools
+}  // namespace art