From 70da334f7b8797c9d7b358b62dcfca8e463def48 Mon Sep 17 00:00:00 2001 From: Pedro Loureiro Date: Fri, 30 Jul 2021 17:27:00 +0000 Subject: [PATCH] Parse new xml attributes used for updatable shared libraries Attributes added to the `library` tag used in AndroidManifest.xml. They allow to easily and transparently include/exclude a library from apps for compatibility purposes. Bug: 191978330 Test: atest com.android.server.pm.parsing.library.ApexSharedLibraryUpdaterTest com.android.server.systemconfig.SystemConfigTest Change-Id: Ibdde742a05fd670a9aaee5ee77ae25b9c0801f53 --- .../java/com/android/server/SystemConfig.java | 87 +++++- .../library/ApexSharedLibraryUpdater.java | 67 +++++ .../library/PackageBackwardCompatibility.java | 11 + .../library/ApexSharedLibraryUpdaterTest.java | 281 ++++++++++++++++++ .../PackageBackwardCompatibilityTest.java | 18 ++ .../server/systemconfig/SystemConfigTest.java | 166 +++++++++++ 6 files changed, 623 insertions(+), 7 deletions(-) create mode 100644 services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java create mode 100644 services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index d7eef0d455b48..ec3dfad2ec2ee 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -108,17 +108,74 @@ public class SystemConfig { public final String name; public final String filename; public final String[] dependencies; + + /** + * SDK version this library was added to the BOOTCLASSPATH. + * + *

At the SDK level specified in this field and higher, the apps' uses-library tags for + * this library will be ignored, since the library is always available on BOOTCLASSPATH. + * + *

0 means not specified. + */ + public final int onBootclasspathSince; + + /** + * SDK version this library was removed from the BOOTCLASSPATH. + * + *

At the SDK level specified in this field and higher, this library needs to be + * explicitly added by apps. For compatibility reasons, when an app + * targets an SDK less than the value of this attribute, this library is automatically + * added. + * + *

0 means not specified. + */ + public final int onBootclasspathBefore; + + /** + * Declares whether this library can be safely ignored from tags. + * + *

This can happen if the library initially had to be explicitly depended-on using that + * tag but has since been moved to the BOOTCLASSPATH which means now is always available + * and the tag is no longer required. + */ + public final boolean canBeSafelyIgnored; + public final boolean isNative; - SharedLibraryEntry(String name, String filename, String[] dependencies) { - this(name, filename, dependencies, false /* isNative */); + + @VisibleForTesting + public SharedLibraryEntry(String name, String filename, String[] dependencies, + boolean isNative) { + this(name, filename, dependencies, 0 /* onBootclasspathSince */, + 0 /* onBootclasspathBefore */, isNative); } - SharedLibraryEntry(String name, String filename, String[] dependencies, boolean isNative) { + @VisibleForTesting + public SharedLibraryEntry(String name, String filename, String[] dependencies, + int onBootclasspathSince, int onBootclassPathBefore) { + this(name, filename, dependencies, onBootclasspathSince, onBootclassPathBefore, + false /* isNative */); + } + + SharedLibraryEntry(String name, String filename, String[] dependencies, + int onBootclasspathSince, int onBootclasspathBefore, boolean isNative) { this.name = name; this.filename = filename; this.dependencies = dependencies; + this.onBootclasspathSince = onBootclasspathSince; + 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; } } @@ -771,11 +828,17 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); } } break; + case "updatable-library": + // "updatable-library" is meant to behave exactly like "library" case "library": { if (allowLibs) { 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); if (lname == null) { Slog.w(TAG, "<" + name + "> without name in " + permFile + " at " + parser.getPositionDescription()); @@ -783,10 +846,20 @@ public class SystemConfig { Slog.w(TAG, "<" + name + "> without file in " + permFile + " at " + parser.getPositionDescription()); } else { - //Log.i(TAG, "Got library " + lname + " in " + lfile); - SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile, - ldependency == null ? new String[0] : ldependency.split(":")); - mSharedLibraries.put(lname, entry); + boolean allowedMinSdk = minDeviceSdk <= Build.VERSION.SDK_INT; + boolean allowedMaxSdk = + maxDeviceSdk == 0 || maxDeviceSdk >= Build.VERSION.SDK_INT; + if (allowedMinSdk && allowedMaxSdk) { + int bcpSince = XmlUtils.readIntAttribute(parser, + "on-bootclasspath-since", 0); + int bcpBefore = XmlUtils.readIntAttribute(parser, + "on-bootclasspath-before", 0); + SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile, + ldependency == null + ? new String[0] : ldependency.split(":"), + bcpSince, bcpBefore); + mSharedLibraries.put(lname, entry); + } } } else { logNotAllowedInPartition(name, permFile, parser); 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 new file mode 100644 index 0000000000000..0418afbf29ee9 --- /dev/null +++ b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java @@ -0,0 +1,67 @@ +/* + * 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. + */ + +package com.android.server.pm.parsing.library; + +import android.util.ArrayMap; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.SystemConfig; +import com.android.server.pm.parsing.pkg.ParsedPackage; + +/** + * Updates packages to add or remove dependencies on shared libraries as per attributes + * in the library declaration + * + * @hide + */ +@VisibleForTesting +public class ApexSharedLibraryUpdater extends PackageSharedLibraryUpdater { + + /** + * ArrayMap like the one you find in {@link SystemConfig}. The keys are the library names. + */ + private final ArrayMap mSharedLibraries; + + public ApexSharedLibraryUpdater( + ArrayMap sharedLibraries) { + mSharedLibraries = sharedLibraries; + } + + @Override + public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) { + final int builtInLibCount = mSharedLibraries.size(); + for (int i = 0; i < builtInLibCount; i++) { + updateSharedLibraryForPackage(mSharedLibraries.valueAt(i), parsedPackage); + } + } + + private void updateSharedLibraryForPackage(SystemConfig.SharedLibraryEntry entry, + ParsedPackage parsedPackage) { + if (entry.onBootclasspathBefore != 0 + && parsedPackage.getTargetSdkVersion() < 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); + } + + if (entry.canBeSafelyIgnored) { + // the library is now present in the BCP and always available; we don't need to add + // it a second time + removeLibrary(parsedPackage, entry.name); + } + } +} diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java index 4334bf6d32f29..bbf584df04050 100644 --- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java +++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java @@ -25,6 +25,7 @@ import android.content.pm.parsing.ParsingPackage; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.SystemConfig; import com.android.server.pm.parsing.pkg.ParsedPackage; import java.util.ArrayList; @@ -63,6 +64,11 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { boolean bootClassPathContainsATB = !addUpdaterForAndroidTestBase(packageUpdaters); + // ApexSharedLibraryUpdater should be the last one, to allow modifications introduced by + // mainline after dessert release. + packageUpdaters.add(new ApexSharedLibraryUpdater( + SystemConfig.getInstance().getSharedLibraries())); + PackageSharedLibraryUpdater[] updaterArray = packageUpdaters .toArray(new PackageSharedLibraryUpdater[0]); INSTANCE = new PackageBackwardCompatibility( @@ -106,6 +112,11 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { private final PackageSharedLibraryUpdater[] mPackageUpdaters; + @VisibleForTesting + PackageSharedLibraryUpdater[] getPackageUpdaters() { + return mPackageUpdaters; + } + private PackageBackwardCompatibility( boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) { this.mBootClassPathContainsATB = bootClassPathContainsATB; 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 new file mode 100644 index 0000000000000..1d9ea4b6028c0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java @@ -0,0 +1,281 @@ +/* + * 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. + */ + +package com.android.server.pm.parsing.library; + +import android.os.Build; +import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; + +import androidx.test.filters.SmallTest; + +import com.android.server.SystemConfig; +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.parsing.pkg.PackageImpl; +import com.android.server.pm.parsing.pkg.ParsedPackage; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + + +/** + * Test for {@link ApexSharedLibraryUpdater} + */ +@Presubmit +@SmallTest +@RunWith(JUnit4.class) +public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTest { + + private final ArrayMap mSharedLibraries = + new ArrayMap<>(8); + + @Before + public void setUp() throws Exception { + installSharedLibraries(); + } + + 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); + } + + private void insertLibrary(String libraryName, int onBootclasspathSince, + int onBootclasspathBefore) { + mSharedLibraries.put(libraryName, new SystemConfig.SharedLibraryEntry( + libraryName, + "foo.jar", + new String[0] /* dependencies */, + onBootclasspathSince, + onBootclasspathBefore + ) + ); + } + + @Test + public void testRegularAppOnRPlus() { + // platform Q should have changes (tested below) + + // these should have no changes + checkNoChanges(Build.VERSION_CODES.R); + checkNoChanges(Build.VERSION_CODES.S); + checkNoChanges(Build.VERSION_CODES.TIRAMISU); + checkNoChanges(Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + private void checkNoChanges(int targetSdkVersion) { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(targetSdkVersion) + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(targetSdkVersion) + .hideAsParsed()) + .hideAsFinal(); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void testBcpSince30Applied() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .addUsesLibrary("fooBcpSince30") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .hideAsParsed()) + .hideAsFinal(); + + // note: target sdk is not what matters in this logic. It's the system SDK + // should be removed because on 30+ (R+) it is implicit + + checkBackwardsCompatibility(before, after); + } + + @Test + public void testBcpSince11kNotAppliedWithoutLibrary() { + 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(); + + // note: target sdk is not what matters in this logic. It's the system SDK + // nothing should change because the implicit from is only from a future platform release + checkBackwardsCompatibility(before, after); + } + + @Test + public void testBcpSince11kNotAppliedWithLibrary() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .addUsesLibrary("fooFromFuture") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .addUsesLibrary("fooFromFuture") + .hideAsParsed()) + .hideAsFinal(); + + // note: target sdk is not what matters in this logic. It's the system SDK + // nothing should change because the implicit from is only from a future platform release + checkBackwardsCompatibility(before, after); + } + + @Test + public void testBcpBefore30NotApplied() { + 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(); + + // should not be affected because it is still in the BCP in 30 / R + checkBackwardsCompatibility(before, after); + } + + @Test + public void testBcpBefore30Applied() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.Q) + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.Q) + .addUsesLibrary("fooBcpBefore30") + .hideAsParsed()) + .hideAsFinal(); + + // should be present because this was in BCP in 29 / Q + 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. All of this happening before the current + * SDK. + */ + @Test + public void testBcpRemovedThenAddedPast() { + insertLibrary("fooBcpRemovedThenAdded", 30, 28); + + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.N) + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.N) + .addUsesLibrary("fooBcpBefore30") + .hideAsParsed()) + .hideAsFinal(); + + // the library is now in the BOOTCLASSPATH (for the second time) so it doesn't need to be + // listed + 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. The first part happening before the + * current SDK and the second part after. + */ + @Test + public void testBcpRemovedThenAddedMiddle_targetQ() { + insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30); + + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.Q) + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.Q) + .addUsesLibrary("fooBcpRemovedThenAdded") + .addUsesLibrary("fooBcpBefore30") + .hideAsParsed()) + .hideAsFinal(); + + // in this example, we are at the point where the library is not in the BOOTCLASSPATH. + // Because the app targets Q / 29 (when this library was in the BCP) then we need to add it + 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. The first part happening before the + * current SDK and the second part after. + */ + @Test + public void testBcpRemovedThenAddedMiddle_targetR() { + insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30); + + 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 not in the BOOTCLASSPATH. + // Because the app targets R/30 (when this library was removed from the BCP) then we don't + //need to add it + 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. The first part happening before the + * current SDK and the second part after. + */ + @Test + public void testBcpRemovedThenAddedMiddle_targetR_usingLib() { + insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30); + + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .addUsesLibrary("fooBcpRemovedThenAdded") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.R) + .addUsesLibrary("fooBcpRemovedThenAdded") + .hideAsParsed()) + .hideAsFinal(); + + // in this example, we are at the point where the library is not in the BOOTCLASSPATH. + // Because the app wants to use the library, it needs to be present + 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/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java index 9768f176ea859..5bcd0f6bb0293 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java @@ -21,6 +21,8 @@ import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_T import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_TEST_RUNNER; import static com.android.server.pm.parsing.library.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; +import static com.google.common.truth.Truth.assertThat; + import android.content.pm.parsing.ParsingPackage; import android.os.Build; import android.platform.test.annotations.Presubmit; @@ -182,6 +184,22 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal()); } + /** + * Ensures that ApexSharedLibraryUpdater is the last updater in the list of package updaters + * used by PackageBackwardCompatibility. + * + * This is required so mainline can add and remove libraries installed by the platform updaters. + */ + @Test + public void testApexPackageUpdaterOrdering() { + PackageBackwardCompatibility instance = + (PackageBackwardCompatibility) PackageBackwardCompatibility.getInstance(); + PackageSharedLibraryUpdater[] updaterArray = instance.getPackageUpdaters(); + + PackageSharedLibraryUpdater lastUpdater = updaterArray[updaterArray.length - 1]; + assertThat(lastUpdater).isInstanceOf(ApexSharedLibraryUpdater.class); + } + private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) { checkBackwardsCompatibility(before, after, PackageBackwardCompatibility::getInstance); } 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 cc531607b4932..4dcd633b45604 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.testng.Assert.expectThrows; +import android.os.Build; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; @@ -329,6 +330,164 @@ public class SystemConfigTest { assertThat(mSysConfig.getAllowedVendorApexes()).isEmpty(); } + /** + * Tests that readPermissions works correctly for a library with on-bootclasspath-before + * and on-bootclasspath-since. + */ + @Test + public void readPermissions_allowLibs_parsesSimpleLibrary() throws IOException { + String contents = + "\n" + + " \n\n" + + " "; + parseSharedLibraries(contents); + assertFooIsOnlySharedLibrary(); + SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); + assertThat(entry.onBootclasspathBefore).isEqualTo(10); + assertThat(entry.onBootclasspathSince).isEqualTo(20); + } + + /** + * Tests that readPermissions works correctly for a library using the new + * {@code updatable-library} tag. + */ + @Test + public void readPermissions_allowLibs_parsesUpdatableLibrary() throws IOException { + String contents = + "\n" + + " \n\n" + + " "; + parseSharedLibraries(contents); + assertFooIsOnlySharedLibrary(); + SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); + assertThat(entry.onBootclasspathBefore).isEqualTo(10); + assertThat(entry.onBootclasspathSince).isEqualTo(20); + } + + /** + * Tests that readPermissions for a library with {@code min-device-sdk} lower than the current + * SDK results in the library being added to the shared libraries. + */ + @Test + public void readPermissions_allowLibs_allowsOldMinSdk() throws IOException { + String contents = + "\n" + + " \n\n" + + " "; + parseSharedLibraries(contents); + assertFooIsOnlySharedLibrary(); + } + + /** + * Tests that readPermissions for a library with {@code min-device-sdk} equal to the current + * SDK results in the library being added to the shared libraries. + */ + @Test + public void readPermissions_allowLibs_allowsCurrentMinSdk() throws IOException { + String contents = + "\n" + + " \n\n" + + " "; + parseSharedLibraries(contents); + assertFooIsOnlySharedLibrary(); + } + + /** + * Tests that readPermissions for a library with {@code min-device-sdk} greater than the current + * SDK results in the library being ignored. + */ + @Test + public void readPermissions_allowLibs_ignoresMinSdkInFuture() throws IOException { + String contents = + "\n" + + " \n\n" + + " "; + parseSharedLibraries(contents); + assertThat(mSysConfig.getSharedLibraries()).isEmpty(); + } + + /** + * Tests that readPermissions for a library with {@code max-device-sdk} less than the current + * SDK results in the library being ignored. + */ + @Test + public void readPermissions_allowLibs_ignoredOldMaxSdk() throws IOException { + String contents = + "\n" + + " \n\n" + + " "; + parseSharedLibraries(contents); + assertThat(mSysConfig.getSharedLibraries()).isEmpty(); + } + + /** + * Tests that readPermissions for a library with {@code max-device-sdk} equal to the current + * SDK results in the library being added to the shared libraries. + */ + @Test + public void readPermissions_allowLibs_allowsCurrentMaxSdk() throws IOException { + String contents = + "\n" + + " \n\n" + + " "; + parseSharedLibraries(contents); + assertFooIsOnlySharedLibrary(); + } + + /** + * Tests that readPermissions for a library with {@code max-device-sdk} greater than the current + * SDK results in the library being added to the shared libraries. + */ + @Test + public void readPermissions_allowLibs_allowsMaxSdkInFuture() throws IOException { + String contents = + "\n" + + " \n\n" + + " "; + parseSharedLibraries(contents); + assertFooIsOnlySharedLibrary(); + } + + private void parseSharedLibraries(String contents) throws IOException { + File folder = createTempSubfolder("permissions_folder"); + createTempFile(folder, "permissions.xml", contents); + readPermissions(folder, /* permissionFlag = ALLOW_LIBS */ 0x02); + } + /** * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. * @@ -366,4 +525,11 @@ public class SystemConfigTest { return folder; } + + private void assertFooIsOnlySharedLibrary() { + assertThat(mSysConfig.getSharedLibraries().size()).isEqualTo(1); + SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); + assertThat(entry.name).isEqualTo("foo"); + assertThat(entry.filename).isEqualTo("foo.jar"); + } }