diff options
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\"") } |