diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 1feb5d415e168..c825f770c0c3e 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -46,6 +46,7 @@ import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; +import com.android.modules.utils.build.UnboundedSdkLevel; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -126,7 +127,7 @@ public class SystemConfig { * *

0 means not specified. */ - public final int onBootclasspathSince; + public final String onBootclasspathSince; /** * SDK version this library was removed from the BOOTCLASSPATH. @@ -138,7 +139,7 @@ public class SystemConfig { * *

0 means not specified. */ - public final int onBootclasspathBefore; + public final String onBootclasspathBefore; /** * Declares whether this library can be safely ignored from tags. @@ -155,19 +156,19 @@ public class SystemConfig { @VisibleForTesting public SharedLibraryEntry(String name, String filename, String[] dependencies, boolean isNative) { - this(name, filename, dependencies, 0 /* onBootclasspathSince */, - 0 /* onBootclasspathBefore */, isNative); + this(name, filename, dependencies, null /* onBootclasspathSince */, + null /* onBootclasspathBefore */, isNative); } @VisibleForTesting public SharedLibraryEntry(String name, String filename, String[] dependencies, - int onBootclasspathSince, int onBootclassPathBefore) { - this(name, filename, dependencies, onBootclasspathSince, onBootclassPathBefore, + String onBootclasspathSince, String onBootclasspathBefore) { + this(name, filename, dependencies, onBootclasspathSince, onBootclasspathBefore, false /* isNative */); } SharedLibraryEntry(String name, String filename, String[] dependencies, - int onBootclasspathSince, int onBootclasspathBefore, boolean isNative) { + String onBootclasspathSince, String onBootclasspathBefore, boolean isNative) { this.name = name; this.filename = filename; this.dependencies = dependencies; @@ -175,16 +176,14 @@ public class SystemConfig { this.onBootclasspathBefore = onBootclasspathBefore; this.isNative = isNative; - canBeSafelyIgnored = this.onBootclasspathSince != 0 - && isSdkAtLeast(this.onBootclasspathSince); - } - - private static boolean isSdkAtLeast(int level) { - if ("REL".equals(Build.VERSION.CODENAME)) { - return Build.VERSION.SDK_INT >= level; - } - return level == Build.VERSION_CODES.CUR_DEVELOPMENT - || Build.VERSION.SDK_INT >= level; + // this entry can be ignored if either: + // - onBootclasspathSince is set and we are at or past that SDK + // - onBootclasspathBefore is set and we are before that SDK + canBeSafelyIgnored = + (this.onBootclasspathSince != null + && UnboundedSdkLevel.isAtLeast(this.onBootclasspathSince)) + || (this.onBootclasspathBefore != null + && !UnboundedSdkLevel.isAtLeast(this.onBootclasspathBefore)); } } @@ -878,10 +877,8 @@ public class SystemConfig { String lname = parser.getAttributeValue(null, "name"); String lfile = parser.getAttributeValue(null, "file"); String ldependency = parser.getAttributeValue(null, "dependency"); - int minDeviceSdk = XmlUtils.readIntAttribute(parser, "min-device-sdk", - 0); - int maxDeviceSdk = XmlUtils.readIntAttribute(parser, "max-device-sdk", - 0); + String minDeviceSdk = parser.getAttributeValue(null, "min-device-sdk"); + String maxDeviceSdk = parser.getAttributeValue(null, "max-device-sdk"); if (lname == null) { Slog.w(TAG, "<" + name + "> without name in " + permFile + " at " + parser.getPositionDescription()); @@ -889,15 +886,18 @@ public class SystemConfig { Slog.w(TAG, "<" + name + "> without file in " + permFile + " at " + parser.getPositionDescription()); } else { - boolean allowedMinSdk = minDeviceSdk <= Build.VERSION.SDK_INT; + boolean allowedMinSdk = + minDeviceSdk == null || UnboundedSdkLevel.isAtLeast( + minDeviceSdk); boolean allowedMaxSdk = - maxDeviceSdk == 0 || maxDeviceSdk >= Build.VERSION.SDK_INT; + maxDeviceSdk == null || UnboundedSdkLevel.isAtMost( + maxDeviceSdk); final boolean exists = new File(lfile).exists(); if (allowedMinSdk && allowedMaxSdk && exists) { - int bcpSince = XmlUtils.readIntAttribute(parser, - "on-bootclasspath-since", 0); - int bcpBefore = XmlUtils.readIntAttribute(parser, - "on-bootclasspath-before", 0); + String bcpSince = parser.getAttributeValue(null, + "on-bootclasspath-since"); + String bcpBefore = parser.getAttributeValue(null, + "on-bootclasspath-before"); SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile, ldependency == null ? new String[0] : ldependency.split(":"), diff --git a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java index 0418afbf29ee9..1a2ff264319e4 100644 --- a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java +++ b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java @@ -19,6 +19,7 @@ package com.android.server.pm.parsing.library; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.UnboundedSdkLevel; import com.android.server.SystemConfig; import com.android.server.pm.parsing.pkg.ParsedPackage; @@ -51,8 +52,11 @@ public class ApexSharedLibraryUpdater extends PackageSharedLibraryUpdater { private void updateSharedLibraryForPackage(SystemConfig.SharedLibraryEntry entry, ParsedPackage parsedPackage) { - if (entry.onBootclasspathBefore != 0 - && parsedPackage.getTargetSdkVersion() < entry.onBootclasspathBefore) { + if (entry.onBootclasspathBefore != null + && isTargetSdkAtMost( + parsedPackage.getTargetSdkVersion(), + entry.onBootclasspathBefore) + && UnboundedSdkLevel.isAtLeast(entry.onBootclasspathBefore)) { // this package targets an API where this library was in the BCP, so add // the library transparently in case the package is using it prefixRequiredLibrary(parsedPackage, entry.name); @@ -64,4 +68,19 @@ public class ApexSharedLibraryUpdater extends PackageSharedLibraryUpdater { removeLibrary(parsedPackage, entry.name); } } + + private static boolean isTargetSdkAtMost(int targetSdk, String onBcpBefore) { + if (isCodename(onBcpBefore)) { + return targetSdk < 10000; + } + return targetSdk < Integer.parseInt(onBcpBefore); + } + + private static boolean isCodename(String version) { + if (version.length() == 0) { + throw new IllegalArgumentException(); + } + // assume Android codenames start with upper case letters. + return Character.isUpperCase((version.charAt(0))); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java index 1d9ea4b6028c0..0b144dc7ee3ab 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java @@ -41,6 +41,8 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTest { + private static final String SDK_INT_PLUS_ONE = "" + (Build.VERSION.SDK_INT + 1); + private static final String SDK_INT_PLUS_TWO = "" + (Build.VERSION.SDK_INT + 2); private final ArrayMap mSharedLibraries = new ArrayMap<>(8); @@ -51,14 +53,19 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes private void installSharedLibraries() throws Exception { mSharedLibraries.clear(); - insertLibrary("foo", 0, 0); - insertLibrary("fooBcpSince30", 30, 0); - insertLibrary("fooBcpBefore30", 0, 30); - insertLibrary("fooFromFuture", Build.VERSION.SDK_INT + 2, 0); + insertLibrary("foo", null, null); + insertLibrary("fooBcpSince30", "30", null); + insertLibrary("fooBcpBefore30", null, "30"); + // simulate libraries being added to the BCP in a future release + insertLibrary("fooSinceFuture", SDK_INT_PLUS_ONE, null); + insertLibrary("fooSinceFutureCodename", "Z", null); + // simulate libraries being removed from the BCP in a future release + insertLibrary("fooBcpBeforeFuture", null, SDK_INT_PLUS_ONE); + insertLibrary("fooBcpBeforeFutureCodename", null, "Z"); } - private void insertLibrary(String libraryName, int onBootclasspathSince, - int onBootclasspathBefore) { + private void insertLibrary(String libraryName, String onBootclasspathSince, + String onBootclasspathBefore) { mSharedLibraries.put(libraryName, new SystemConfig.SharedLibraryEntry( libraryName, "foo.jar", @@ -112,7 +119,7 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes } @Test - public void testBcpSince11kNotAppliedWithoutLibrary() { + public void testBcpSinceFutureNotAppliedWithoutLibrary() { ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) .setTargetSdkVersion(Build.VERSION_CODES.R) .hideAsParsed()); @@ -128,15 +135,17 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes } @Test - public void testBcpSince11kNotAppliedWithLibrary() { + public void testBcpSinceFutureNotAppliedWithLibrary() { ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) .setTargetSdkVersion(Build.VERSION_CODES.R) - .addUsesLibrary("fooFromFuture") + .addUsesLibrary("fooSinceFuture") + .addUsesLibrary("fooSinceFutureCodename") .hideAsParsed()); AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) .setTargetSdkVersion(Build.VERSION_CODES.R) - .addUsesLibrary("fooFromFuture") + .addUsesLibrary("fooSinceFuture") + .addUsesLibrary("fooSinceFutureCodename") .hideAsParsed()) .hideAsFinal(); @@ -183,7 +192,7 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes */ @Test public void testBcpRemovedThenAddedPast() { - insertLibrary("fooBcpRemovedThenAdded", 30, 28); + insertLibrary("fooBcpRemovedThenAdded", "30", "28"); ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) .setTargetSdkVersion(Build.VERSION_CODES.N) @@ -207,7 +216,8 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes */ @Test public void testBcpRemovedThenAddedMiddle_targetQ() { - insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30); + insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_ONE, "30"); + insertLibrary("fooBcpRemovedThenAddedCodename", "Z", "30"); ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) .setTargetSdkVersion(Build.VERSION_CODES.Q) @@ -217,6 +227,7 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes .setTargetSdkVersion(Build.VERSION_CODES.Q) .addUsesLibrary("fooBcpRemovedThenAdded") .addUsesLibrary("fooBcpBefore30") + .addUsesLibrary("fooBcpRemovedThenAddedCodename") .hideAsParsed()) .hideAsFinal(); @@ -232,7 +243,8 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes */ @Test public void testBcpRemovedThenAddedMiddle_targetR() { - insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30); + insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_ONE, "30"); + insertLibrary("fooBcpRemovedThenAddedCodename", "Z", "30"); ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) .setTargetSdkVersion(Build.VERSION_CODES.R) @@ -256,7 +268,8 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes */ @Test public void testBcpRemovedThenAddedMiddle_targetR_usingLib() { - insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30); + insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_ONE, "30"); + insertLibrary("fooBcpRemovedThenAddedCodename", "Z", "30"); ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) .setTargetSdkVersion(Build.VERSION_CODES.R) @@ -274,6 +287,82 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes checkBackwardsCompatibility(before, after); } + /** + * Test a library that was first removed from the BCP [to a mainline module] and later was + * moved back to the BCP via a mainline module update. Both things happening in future SDKs. + */ + @Test + public void testBcpRemovedThenAddedFuture() { + insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_TWO, SDK_INT_PLUS_ONE); + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .hideAsParsed()) + .hideAsFinal(); + + // in this example, we are at the point where the library is still in the BCP + checkBackwardsCompatibility(before, after); + } + + /** + * Test a library that was first removed from the BCP [to a mainline module] and later was + * moved back to the BCP via a mainline module update. Both things happening in future SDKs. + */ + @Test + public void testBcpRemovedThenAddedFuture_usingLib() { + insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_TWO, SDK_INT_PLUS_ONE); + + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE)) + .addUsesLibrary("fooBcpRemovedThenAdded") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE)) + .hideAsParsed()) + .hideAsFinal(); + + // in this example, we are at the point where the library was removed from the BCP + checkBackwardsCompatibility(before, after); + } + + @Test + public void testBcpBeforeFuture() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .addUsesLibrary("fooBcpBeforeFuture") + .addUsesLibrary("fooBcpBeforeFutureCodename") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .hideAsParsed()) + .hideAsFinal(); + + // in this example, we are at the point where the library was removed from the BCP + checkBackwardsCompatibility(before, after); + } + + @Test + public void testBcpBeforeFuture_futureTargetSdk() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE)) + .addUsesLibrary("fooBcpBeforeFuture") + .addUsesLibrary("fooBcpBeforeFutureCodename") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE)) + .hideAsParsed()) + .hideAsFinal(); + + // in this example, we are at the point where the library was removed from the BCP + checkBackwardsCompatibility(before, after); + } + private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) { checkBackwardsCompatibility(before, after, () -> new ApexSharedLibraryUpdater(mSharedLibraries)); diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index bfdffc0e65677..20486b3e396dc 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -429,18 +429,40 @@ public class SystemConfigTest { public void readPermissions_allowLibs_parsesSimpleLibrary() throws IOException { String contents = "\n" - + " \n\n" - + " "; + + " \n\n" + + " "; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); - assertThat(entry.onBootclasspathBefore).isEqualTo(10); - assertThat(entry.onBootclasspathSince).isEqualTo(20); + assertThat(entry.onBootclasspathBefore).isEqualTo("10"); + assertThat(entry.onBootclasspathSince).isEqualTo("20"); + } + + /** + * Tests that readPermissions works correctly for a library with on-bootclasspath-before + * and on-bootclasspath-since that uses codenames. + */ + @Test + public void readPermissions_allowLibs_parsesSimpleLibraryWithCodenames() throws IOException { + String contents = + "\n" + + " \n\n" + + " "; + parseSharedLibraries(contents); + assertFooIsOnlySharedLibrary(); + SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); + assertThat(entry.onBootclasspathBefore).isEqualTo("Q"); + assertThat(entry.onBootclasspathSince).isEqualTo("W"); } /** @@ -461,8 +483,8 @@ public class SystemConfigTest { parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); - assertThat(entry.onBootclasspathBefore).isEqualTo(10); - assertThat(entry.onBootclasspathSince).isEqualTo(20); + assertThat(entry.onBootclasspathBefore).isEqualTo("10"); + assertThat(entry.onBootclasspathSince).isEqualTo("20"); } /** @@ -543,12 +565,20 @@ public class SystemConfigTest { */ @Test public void readPermissions_allowLibs_allowsCurrentMaxSdk() throws IOException { + // depending on whether this test is running before or after finalization, we need to + // pass a different parameter + String parameter; + if ("REL".equals(Build.VERSION.CODENAME)) { + parameter = "" + Build.VERSION.SDK_INT; + } else { + parameter = "ZZZ"; + } String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents);