summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff Sharkey <jsharkey@google.com> 2024-01-19 13:39:08 -0700
committer Jeff Sharkey <jsharkey@google.com> 2024-01-24 11:43:36 -0700
commit950697a4b6bb91e7c68be1b70d5b067712518a65 (patch)
tree861a6d254114298bef28b7c9f27012d20c32333e
parentff93fc4025578ba4b64b9b0d9d2f4c084c45cda4 (diff)
Ravenwood support for `SystemProperties`.
One of our final missing pieces of foundational functionality is the SystemProperties key/value store. Over the years, this key/value store has been (ab)used to configure very obscure parts of the OS. As tempting as it might be to simply let code rely on default return values when a key is undefined, we'd like to ensure that code owners carefully confirm any assumed behaviors. To accomplish this, we default to blocking both read/write access to keys until their use has been explicitly audited. Based on our guiding principles, as code owners support their APIs under Ravenwood, they're expected to bring along all relevant tests, which will uncover SystemProperties usage that needs triage, reducing the risk of downstream clients uncovering that usage. Tests can explicitly allow read/write access to specific properties via their RavenwoodRule.Builder definition, which is also how we ensure that all values are consistently reset between tests. Bug: 319647875 Test: atest FrameworksCoreSystemPropertiesTestsRavenwood Test: atest FrameworksCoreTestsRavenwood CtsOsTestCasesRavenwood Change-Id: I6510e06c33ee8b2bf31b58f35faa07127ecd16b7
-rwxr-xr-xcore/java/android/os/Build.java4
-rw-r--r--core/java/android/os/SystemProperties.java31
-rw-r--r--core/tests/coretests/src/android/os/BuildTest.java2
-rw-r--r--core/tests/systemproperties/Android.bp22
-rw-r--r--core/tests/systemproperties/src/android/os/SystemPropertiesTest.java29
-rw-r--r--ravenwood/framework-minus-apex-ravenwood-policies.txt3
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java9
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java36
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java175
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt2
-rw-r--r--services/tests/powerstatstests/Android.bp1
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java57
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java167
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt14
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt15
15 files changed, 535 insertions, 32 deletions
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 58717179d64d..3977bdf413d9 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -28,6 +28,7 @@ import android.app.ActivityThread;
import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.sysprop.DeviceProperties;
import android.sysprop.SocProperties;
import android.sysprop.TelephonyProperties;
@@ -47,6 +48,7 @@ import java.util.stream.Collectors;
/**
* Information about the current build, extracted from system properties.
*/
+@RavenwoodKeepWholeClass
public class Build {
private static final String TAG = "Build";
@@ -307,7 +309,7 @@ public class Build {
* compatibility.
*/
final String[] abiList;
- if (VMRuntime.getRuntime().is64Bit()) {
+ if (android.os.Process.is64Bit()) {
abiList = SUPPORTED_64_BIT_ABIS;
} else {
abiList = SUPPORTED_32_BIT_ABIS;
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index aa283a2d019b..a818919d184e 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -20,6 +20,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
import android.util.Log;
import android.util.MutableInt;
@@ -36,6 +38,8 @@ import java.security.NoSuchAlgorithmException;
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
@@ -51,6 +55,8 @@ import java.util.HashMap;
* {@hide}
*/
@SystemApi
+@RavenwoodKeepWholeClass
+@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host")
public class SystemProperties {
private static final String TAG = "SystemProperties";
private static final boolean TRACK_KEY_ACCESS = false;
@@ -94,6 +100,31 @@ public class SystemProperties {
}
}
+ /** @hide */
+ public static void init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
+ native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate,
+ SystemProperties::callChangeCallbacks);
+ synchronized (sChangeCallbacks) {
+ sChangeCallbacks.clear();
+ }
+ }
+
+ /** @hide */
+ public static void reset$ravenwood() {
+ native_reset$ravenwood();
+ synchronized (sChangeCallbacks) {
+ sChangeCallbacks.clear();
+ }
+ }
+
+ // These native methods are currently only implemented by Ravenwood, as it's the only
+ // mechanism we have to jump to our RavenwoodNativeSubstitutionClass
+ private static native void native_init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+ Runnable changeCallback);
+ private static native void native_reset$ravenwood();
+
// 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
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 2d3e12331e23..2a718ff2f4aa 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -20,7 +20,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
@@ -71,7 +70,6 @@ public class BuildTest {
*/
@Test
@SmallTest
- @IgnoreUnderRavenwood(blockedBy = Build.class)
public void testBuildFields() throws Exception {
assertNotEmpty("ID", Build.ID);
assertNotEmpty("DISPLAY", Build.DISPLAY);
diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp
index 765ca3e5110a..21aa3c44044b 100644
--- a/core/tests/systemproperties/Android.bp
+++ b/core/tests/systemproperties/Android.bp
@@ -15,6 +15,9 @@ android_test {
static_libs: [
"android-common",
"frameworks-core-util-lib",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "ravenwood-junit",
],
libs: [
"android.test.runner",
@@ -23,3 +26,22 @@ android_test {
platform_apis: true,
certificate: "platform",
}
+
+android_ravenwood_test {
+ name: "FrameworksCoreSystemPropertiesTestsRavenwood",
+ static_libs: [
+ "android-common",
+ "frameworks-core-util-lib",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "ravenwood-junit",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+ auto_gen_config: true,
+}
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 67783bff9299..ea65de088c07 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -16,19 +16,36 @@
package android.os;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
import android.test.suitebuilder.annotation.SmallTest;
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-public class SystemPropertiesTest extends TestCase {
+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";
+ @Test
@SmallTest
public void testStressPersistPropertyConsistency() throws Exception {
for (int i = 0; i < 100; ++i) {
@@ -38,6 +55,7 @@ public class SystemPropertiesTest extends TestCase {
}
}
+ @Test
@SmallTest
public void testStressMemoryPropertyConsistency() throws Exception {
for (int i = 0; i < 100; ++i) {
@@ -47,6 +65,7 @@ public class SystemPropertiesTest extends TestCase {
}
}
+ @Test
@SmallTest
public void testProperties() throws Exception {
String value;
@@ -93,6 +112,7 @@ public class SystemPropertiesTest extends TestCase {
assertEquals(expected, value);
}
+ @Test
@SmallTest
public void testHandle() throws Exception {
String value;
@@ -114,6 +134,7 @@ public class SystemPropertiesTest extends TestCase {
assertEquals(12345, handle.getInt(12345));
}
+ @Test
@SmallTest
public void testIntegralProperties() throws Exception {
testInt("", 123, 123);
@@ -133,6 +154,7 @@ public class SystemPropertiesTest extends TestCase {
testLong("-3147483647", 124, -3147483647L);
}
+ @Test
@SmallTest
public void testUnset() throws Exception {
assertEquals("abc", SystemProperties.get(UNSET_KEY, "abc"));
@@ -142,6 +164,7 @@ public class SystemPropertiesTest extends TestCase {
assertEquals(-10, SystemProperties.getLong(UNSET_KEY, -10));
}
+ @Test
@SmallTest
@SuppressWarnings("null")
public void testNullKey() throws Exception {
@@ -176,6 +199,7 @@ public class SystemPropertiesTest extends TestCase {
}
}
+ @Test
@SmallTest
public void testCallbacks() {
// Latches are not really necessary, but are easy to use.
@@ -220,6 +244,7 @@ public class SystemPropertiesTest extends TestCase {
}
}
+ @Test
@SmallTest
public void testDigestOf() {
final String empty = SystemProperties.digestOf();
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index f5e4af50fc29..c696a4bf7d47 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -6,6 +6,9 @@ class :aidl stubclass
# Keep all feature flag implementations
class :feature_flags stubclass
+# Keep all sysprops generated code implementations
+class :sysprops stubclass
+
# Collections
class android.util.ArrayMap stubclass
class android.util.ArraySet stubclass
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index eacdc2f79254..91c522e82cce 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -19,8 +19,6 @@ package android.platform.test.ravenwood;
import android.os.HandlerThread;
import android.os.Looper;
-import java.util.Objects;
-
public class RavenwoodRuleImpl {
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
@@ -31,6 +29,10 @@ public class RavenwoodRuleImpl {
public static void init(RavenwoodRule rule) {
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
+ android.os.SystemProperties.init$ravenwood(
+ rule.mSystemProperties.getValues(),
+ rule.mSystemProperties.getKeyReadablePredicate(),
+ rule.mSystemProperties.getKeyWritablePredicate());
com.android.server.LocalServices.removeAllServicesForTest();
@@ -49,7 +51,8 @@ public class RavenwoodRuleImpl {
com.android.server.LocalServices.removeAllServicesForTest();
- android.os.Process.reset$ravenwood();
+ android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
+ android.os.Process.reset$ravenwood();
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 53da8ba14a2c..dd442f08321f 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -62,6 +62,8 @@ public class RavenwoodRule implements TestRule {
boolean mProvideMainThread = false;
+ final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+
public RavenwoodRule() {
}
@@ -98,6 +100,40 @@ public class RavenwoodRule implements TestRule {
return this;
}
+ /**
+ * Configure the given system property as immutable for the duration of the test.
+ * Read access to the key is allowed, and write access will fail. When {@code value} is
+ * {@code null}, the value is left as undefined.
+ *
+ * All properties in the {@code debug.*} namespace are automatically mutable, with no
+ * developer action required.
+ *
+ * Has no effect under non-Ravenwood environments.
+ */
+ public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
+ /* @Nullable */ Object value) {
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadOnly(key);
+ return this;
+ }
+
+ /**
+ * Configure the given system property as mutable for the duration of the test.
+ * Both read and write access to the key is allowed, and its value will be reset between
+ * each test. When {@code value} is {@code null}, the value is left as undefined.
+ *
+ * All properties in the {@code debug.*} namespace are automatically mutable, with no
+ * developer action required.
+ *
+ * Has no effect under non-Ravenwood environments.
+ */
+ public Builder setSystemPropertyMutable(/* @NonNull */ String key,
+ /* @Nullable */ Object value) {
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadWrite(key);
+ return this;
+ }
+
public RavenwoodRule build() {
return mRule;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
new file mode 100644
index 000000000000..85ad4e444f24
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+package android.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+class RavenwoodSystemProperties {
+ private final Map<String, String> mValues = new HashMap<>();
+
+ /** 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 "ro.bootloader":
+ case "ro.debuggable":
+ case "ro.hardware":
+ case "ro.hw_timeout_multiplier":
+ case "ro.odm.build.media_performance_class":
+ 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
+ setValueForPartitions("product.brand", "Android");
+ setValueForPartitions("product.device", "Ravenwood");
+ setValueForPartitions("product.manufacturer", "Android");
+ setValueForPartitions("product.model", "Ravenwood");
+ setValueForPartitions("product.name", "Ravenwood");
+
+ setValueForPartitions("product.cpu.abilist", "x86_64");
+ setValueForPartitions("product.cpu.abilist32", "");
+ setValueForPartitions("product.cpu.abilist64", "x86_64");
+
+ setValueForPartitions("build.date", "Thu Jan 01 00:00:00 GMT 2024");
+ setValueForPartitions("build.date.utc", "1704092400");
+ setValueForPartitions("build.id", "MAIN");
+ setValueForPartitions("build.tags", "dev-keys");
+ setValueForPartitions("build.type", "userdebug");
+ setValueForPartitions("build.version.all_codenames", "REL");
+ setValueForPartitions("build.version.codename", "REL");
+ setValueForPartitions("build.version.incremental", "userdebug.ravenwood.20240101");
+ setValueForPartitions("build.version.known_codenames", "REL");
+ setValueForPartitions("build.version.release", "14");
+ setValueForPartitions("build.version.release_or_codename", "VanillaIceCream");
+ setValueForPartitions("build.version.sdk", "34");
+
+ setValue("ro.board.first_api_level", "1");
+ setValue("ro.product.first_api_level", "1");
+
+ setValue("ro.soc.manufacturer", "Android");
+ setValue("ro.soc.model", "Ravenwood");
+
+ setValue("ro.debuggable", "1");
+ }
+
+ Map<String, String> getValues() {
+ return new HashMap<>(mValues);
+ }
+
+ Predicate<String> getKeyReadablePredicate() {
+ return mKeyReadablePredicate;
+ }
+
+ Predicate<String> getKeyWritablePredicate() {
+ return mKeyWritablePredicate;
+ }
+
+ private static final String[] PARTITIONS = {
+ "bootimage",
+ "odm",
+ "product",
+ "system",
+ "system_ext",
+ "vendor",
+ "vendor_dlkm",
+ };
+
+ /**
+ * Set the given property for all possible partitions where it could be defined. For
+ * example, the value of {@code ro.build.type} is typically also mirrored under
+ * {@code ro.system.build.type}, etc.
+ */
+ private void setValueForPartitions(String key, String value) {
+ setValue("ro." + key, value);
+ for (String partition : PARTITIONS) {
+ setValue("ro." + partition + "." + key, value);
+ }
+ }
+
+ public void setValue(String key, Object value) {
+ final String valueString = (value == null) ? null : String.valueOf(value);
+ if ((valueString == null) || valueString.isEmpty()) {
+ mValues.remove(key);
+ } else {
+ mValues.put(key, valueString);
+ }
+ }
+
+ public void setAccessNone(String key) {
+ mKeyReadable.remove(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadOnly(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadWrite(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.add(key);
+ }
+
+ /**
+ * Return the "root" of the given property key, stripping away any modifier prefix such as
+ * {@code ro.} or {@code persist.}.
+ */
+ private static String getKeyRoot(String key) {
+ if (key.startsWith("ro.")) {
+ return key.substring(3);
+ } else if (key.startsWith("persist.")) {
+ return key.substring(8);
+ } else {
+ return key;
+ }
+ }
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index ab2546bab246..3b6ca8a7e90e 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -47,6 +47,7 @@ android.os.BatteryUsageStatsQuery
android.os.Binder
android.os.Binder$IdentitySupplier
android.os.Broadcaster
+android.os.Build
android.os.BundleMerger
android.os.ConditionVariable
android.os.FileUtils
@@ -65,6 +66,7 @@ android.os.PowerComponents
android.os.Process
android.os.ServiceSpecificException
android.os.SystemClock
+android.os.SystemProperties
android.os.ThreadLocalWorkSource
android.os.TimestampedValue
android.os.UidBatteryConsumer
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 654d7a8de168..f49f6383b3c8 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -44,6 +44,7 @@ android_test {
"servicestests-utils",
"platform-test-annotations",
"flag-junit",
+ "ravenwood-junit",
],
libs: [
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index ca162e0b46e1..ba2b53854cd7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -32,6 +32,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -57,7 +58,8 @@ public class BatteryUsageStatsRule implements TestRule {
private final PowerProfile mPowerProfile;
private final MockClock mMockClock = new MockClock();
- private final MockBatteryStatsImpl mBatteryStats;
+ private final File mHistoryDir;
+ private MockBatteryStatsImpl mBatteryStats;
private Handler mHandler;
private BatteryUsageStats mBatteryUsageStats;
@@ -66,6 +68,10 @@ public class BatteryUsageStatsRule implements TestRule {
private SparseArray<int[]> mCpusByPolicy = new SparseArray<>();
private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>();
+ private int mDisplayCount = -1;
+ private int mPerUidModemModel = -1;
+ private NetworkStats mNetworkStats;
+
public BatteryUsageStatsRule() {
this(0, null);
}
@@ -78,16 +84,38 @@ public class BatteryUsageStatsRule implements TestRule {
mHandler = mock(Handler.class);
mPowerProfile = spy(new PowerProfile());
mMockClock.currentTime = currentTime;
- mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler);
- mBatteryStats.setPowerProfile(mPowerProfile);
+ mHistoryDir = historyDir;
+
+ if (!RavenwoodRule.isUnderRavenwood()) {
+ lateInitBatteryStats();
+ }
mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+ }
+
+ private void lateInitBatteryStats() {
+ if (mBatteryStats != null) return;
+
+ mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
+ mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
mBatteryStats.onSystemReady();
+
+ if (mDisplayCount != -1) {
+ mBatteryStats.setDisplayCountLocked(mDisplayCount);
+ }
+ if (mPerUidModemModel != -1) {
+ synchronized (mBatteryStats) {
+ mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+ }
+ }
+ if (mNetworkStats != null) {
+ mBatteryStats.setNetworkStats(mNetworkStats);
+ }
}
public MockClock getMockClock() {
@@ -112,7 +140,10 @@ public class BatteryUsageStatsRule implements TestRule {
}
mCpusByPolicy.put(policy, relatedCpus);
mFreqsByPolicy.put(policy, frequencies);
- mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+ if (mBatteryStats != null) {
+ mBatteryStats.setCpuScalingPolicies(
+ new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+ }
return this;
}
@@ -174,13 +205,19 @@ public class BatteryUsageStatsRule implements TestRule {
public BatteryUsageStatsRule setNumDisplays(int value) {
when(mPowerProfile.getNumDisplays()).thenReturn(value);
- mBatteryStats.setDisplayCountLocked(value);
+ mDisplayCount = value;
+ if (mBatteryStats != null) {
+ mBatteryStats.setDisplayCountLocked(mDisplayCount);
+ }
return this;
}
public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) {
- synchronized (mBatteryStats) {
- mBatteryStats.setPerUidModemModel(perUidModemModel);
+ mPerUidModemModel = perUidModemModel;
+ if (mBatteryStats != null) {
+ synchronized (mBatteryStats) {
+ mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+ }
}
return this;
}
@@ -210,7 +247,10 @@ public class BatteryUsageStatsRule implements TestRule {
}
public void setNetworkStats(NetworkStats networkStats) {
- mBatteryStats.setNetworkStats(networkStats);
+ mNetworkStats = networkStats;
+ if (mBatteryStats != null) {
+ mBatteryStats.setNetworkStats(mNetworkStats);
+ }
}
@Override
@@ -225,6 +265,7 @@ public class BatteryUsageStatsRule implements TestRule {
}
private void before() {
+ lateInitBatteryStats();
HandlerThread bgThread = new HandlerThread("bg thread");
bgThread.start();
mHandler = new Handler(bgThread.getLooper());
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
index 1ec1d5f307e1..2f6a361e3609 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
@@ -15,42 +15,181 @@
*/
package com.android.hoststubgen.nativesubstitution;
+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;
+ /** Callback to trigger when values are changed */
+ @GuardedBy("sLock")
+ private static Runnable sChangeCallback;
+
+ /**
+ * 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<>();
+
+ public static void native_init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+ Runnable changeCallback) {
+ synchronized (sLock) {
+ sValues = Objects.requireNonNull(values);
+ sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate);
+ sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate);
+ sChangeCallback = Objects.requireNonNull(changeCallback);
+ sKeyHandles.clear();
+ }
+ }
+
+ public static void native_reset$ravenwood() {
+ synchronized (sLock) {
+ sValues = null;
+ sKeyReadablePredicate = null;
+ sKeyWritablePredicate = null;
+ sChangeCallback = null;
+ sKeyHandles.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);
+ }
+ sChangeCallback.run();
+ }
+ }
+
public static String native_get(String key, String def) {
- throw new RuntimeException("Not implemented yet");
+ 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) {
- throw new RuntimeException("Not implemented yet");
+ try {
+ return Integer.parseInt(native_get(key, ""));
+ } catch (NumberFormatException ignored) {
+ return def;
+ }
}
+
public static long native_get_long(String key, long def) {
- throw new RuntimeException("Not implemented yet");
+ try {
+ return Long.parseLong(native_get(key, ""));
+ } catch (NumberFormatException ignored) {
+ return def;
+ }
}
+
public static boolean native_get_boolean(String key, boolean def) {
- throw new RuntimeException("Not implemented yet");
+ return parseBoolean(native_get(key, ""), def);
}
public static long native_find(String name) {
- throw new RuntimeException("Not implemented yet");
+ 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) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get(sKeyHandles.get((int) handle), "");
+ }
}
+
public static int native_get_int(long handle, int def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_int(sKeyHandles.get((int) handle), def);
+ }
}
+
public static long native_get_long(long handle, long def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_long(sKeyHandles.get((int) handle), def);
+ }
}
+
public static boolean native_get_boolean(long handle, boolean def) {
- throw new RuntimeException("Not implemented yet");
- }
- public static void native_set(String key, String def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_boolean(sKeyHandles.get((int) handle), def);
+ }
}
+
public static void native_add_change_callback() {
- throw new RuntimeException("Not implemented yet");
+ // Ignored; callback always registered via init above
}
+
public static void native_report_sysprop_change() {
- throw new RuntimeException("Not implemented yet");
+ // Report through callback always registered via init above
+ synchronized (sLock) {
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ sChangeCallback.run();
+ }
+ }
+
+ 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/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 8ca4732f57c4..76bac9286a1f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -24,6 +24,7 @@ class AndroidHeuristicsFilter(
private val classes: ClassNodes,
val aidlPolicy: FilterPolicyWithReason?,
val featureFlagsPolicy: FilterPolicyWithReason?,
+ val syspropsPolicy: FilterPolicyWithReason?,
fallback: OutputFilter
) : DelegatingFilter(fallback) {
override fun getPolicyForClass(className: String): FilterPolicyWithReason {
@@ -33,6 +34,9 @@ class AndroidHeuristicsFilter(
if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) {
return featureFlagsPolicy
}
+ if (syspropsPolicy != null && classes.isSyspropsClass(className)) {
+ return syspropsPolicy
+ }
return super.getPolicyForClass(className)
}
}
@@ -57,3 +61,13 @@ private fun ClassNodes.isFeatureFlagsClass(className: String): Boolean {
|| className.endsWith("/FeatureFlagsImpl")
|| className.endsWith("/FakeFeatureFlagsImpl");
}
+
+/**
+ * @return if a given class "seems like" a sysprops class.
+ */
+private fun ClassNodes.isSyspropsClass(className: String): Boolean {
+ // Matches template classes defined here:
+ // https://cs.android.com/android/platform/superproject/main/+/main:system/tools/sysprop/
+ return className.startsWith("android/sysprop/")
+ && className.endsWith("Properties")
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index d38a6e34e09f..7fdd944770c6 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -64,6 +64,7 @@ fun createFilterFromTextPolicyFile(
var aidlPolicy: FilterPolicyWithReason? = null
var featureFlagsPolicy: FilterPolicyWithReason? = null
+ var syspropsPolicy: FilterPolicyWithReason? = null
try {
BufferedReader(FileReader(filename)).use { reader ->
@@ -141,6 +142,14 @@ fun createFilterFromTextPolicyFile(
featureFlagsPolicy =
policy.withReason("$FILTER_REASON (feature flags)")
}
+ SpecialClass.Sysprops -> {
+ if (syspropsPolicy != null) {
+ throw ParseException(
+ "Policy for sysprops already defined")
+ }
+ syspropsPolicy =
+ policy.withReason("$FILTER_REASON (sysprops)")
+ }
}
}
}
@@ -205,10 +214,10 @@ fun createFilterFromTextPolicyFile(
}
var ret: OutputFilter = imf
- if (aidlPolicy != null || featureFlagsPolicy != null) {
+ if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) {
log.d("AndroidHeuristicsFilter enabled")
ret = AndroidHeuristicsFilter(
- classes, aidlPolicy, featureFlagsPolicy, imf)
+ classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf)
}
return ret
}
@@ -218,6 +227,7 @@ private enum class SpecialClass {
NotSpecial,
Aidl,
FeatureFlags,
+ Sysprops,
}
private fun resolveSpecialClass(className: String): SpecialClass {
@@ -227,6 +237,7 @@ private fun resolveSpecialClass(className: String): SpecialClass {
when (className.lowercase()) {
":aidl" -> return SpecialClass.Aidl
":feature_flags" -> return SpecialClass.FeatureFlags
+ ":sysprops" -> return SpecialClass.Sysprops
}
throw ParseException("Invalid special class name \"$className\"")
}