Use Build.VERSION.KNOWN_CODENAMES in UnboundedSdkLevel.
Bug: 211747008
Test: atest UnbounbdedSdkLevelTest
Change-Id: I707d17caf578fb2219d9b236f2fec51236ef3c2d
diff --git a/java/com/android/modules/utils/build/UnboundedSdkLevel.java b/java/com/android/modules/utils/build/UnboundedSdkLevel.java
index ee44f05..76653e0 100644
--- a/java/com/android/modules/utils/build/UnboundedSdkLevel.java
+++ b/java/com/android/modules/utils/build/UnboundedSdkLevel.java
@@ -22,6 +22,9 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.Collections;
+import java.util.Set;
+
/**
* Utility class to check SDK level on a device.
*
@@ -45,28 +48,42 @@
}
private static final UnboundedSdkLevel sInstance =
- new UnboundedSdkLevel(Build.VERSION.SDK_INT, Build.VERSION.CODENAME);
+ new UnboundedSdkLevel(
+ Build.VERSION.SDK_INT,
+ Build.VERSION.CODENAME,
+ SdkLevel.isAtLeastT()
+ ? Build.VERSION.KNOWN_CODENAMES
+ : Collections.emptySet());
private final int mSdkInt;
private final String mCodename;
private final boolean mIsReleaseBuild;
+ private final Set<String> mKnownCodenames;
@VisibleForTesting
- UnboundedSdkLevel(int sdkInt, String codename) {
+ UnboundedSdkLevel(int sdkInt, String codename, Set<String> knownCodenames) {
mSdkInt = sdkInt;
mCodename = codename;
mIsReleaseBuild = "REL".equals(codename);
+ mKnownCodenames = knownCodenames;
}
@VisibleForTesting
boolean isAtLeastInternal(@NonNull String version) {
if (mIsReleaseBuild) {
- // On release builds we only expect to install artifacts meant for released
- // Android Versions. No codenames.
+ if (isCodename(version)) {
+ // On release builds only accept future codenames
+ if (mKnownCodenames.contains(version)) {
+ throw new IllegalArgumentException("Artifact with a known codename " + version
+ + " must be recompiled with a finalized integer version.");
+ }
+ // mSdkInt is always less than future codenames
+ return false;
+ }
return mSdkInt >= Integer.parseInt(version);
}
if (isCodename(version)) {
- return mCodename.compareTo(version) >= 0;
+ return mKnownCodenames.contains(version);
}
// Never assume what the next SDK level is until SDK finalization completes.
// SDK_INT is always assigned the latest finalized value of the SDK.
@@ -76,18 +93,25 @@
@VisibleForTesting
boolean isAtMostInternal(@NonNull String version) {
if (mIsReleaseBuild) {
- // On release builds we only expect to install artifacts meant for released
- // Android Versions. No codenames.
+ if (isCodename(version)) {
+ // On release builds only accept future codenames
+ if (mKnownCodenames.contains(version)) {
+ throw new IllegalArgumentException("Artifact with a known codename " + version
+ + " must be recompiled with a finalized integer version.");
+ }
+ // mSdkInt is always less than future codenames
+ return true;
+ }
return mSdkInt <= Integer.parseInt(version);
}
if (isCodename(version)) {
- return mCodename.compareTo(version) <= 0;
+ return !mKnownCodenames.contains(version) || mCodename.equals(version);
}
// Never assume what the next SDK level is until SDK finalization completes.
// SDK_INT is always assigned the latest finalized value of the SDK.
//
// Note: multiple releases can be in development at the same time. For example, during
- // Sv2 and Tiramisu development, both builds have SDK_INT=31 which is not a sufficient
+ // Sv2 and Tiramisu development, both builds have SDK_INT=31 which is not sufficient
// information to differentiate between them. Also, "31" at that point already corresponds
// to a previously finalized API level, meaning that the current build is not at most "31".
// This is why the comparison is strict, instead of <=.
diff --git a/javatests/com/android/modules/utils/build/UnboundedSdkLevelTest.java b/javatests/com/android/modules/utils/build/UnboundedSdkLevelTest.java
index acdde8f..c8d4a47 100644
--- a/javatests/com/android/modules/utils/build/UnboundedSdkLevelTest.java
+++ b/javatests/com/android/modules/utils/build/UnboundedSdkLevelTest.java
@@ -25,31 +25,85 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Set;
+
@RunWith(AndroidJUnit4.class)
public class UnboundedSdkLevelTest {
@Test
- public void testFinalizedSdk() {
- UnboundedSdkLevel sdkLevel = new UnboundedSdkLevel(31, "REL");
+ public void testFinalizedSdk_S() {
+ // Test against finalized S / 31 release
+ // Note empty set of known codenames, since on S builds Build.VERSION.KNOWN_CODENAMES
+ // does not exist. This is a realistic unit test reflecting S release builds.
+ UnboundedSdkLevel sdkLevel = new UnboundedSdkLevel(31, "REL", Set.of());
+ assertThat(sdkLevel.isAtLeastInternal("30")).isTrue();
assertThat(sdkLevel.isAtLeastInternal("31")).isTrue();
assertThat(sdkLevel.isAtLeastInternal("32")).isFalse();
- assertThrows(NumberFormatException.class, () -> sdkLevel.isAtLeastInternal(""));
- assertThrows(NumberFormatException.class, () -> sdkLevel.isAtLeastInternal("S"));
+ assertThat(sdkLevel.isAtLeastInternal("R")).isFalse();
+ assertThat(sdkLevel.isAtLeastInternal("S")).isFalse();
+ assertThat(sdkLevel.isAtLeastInternal("Sv2")).isFalse();
+ assertThat(sdkLevel.isAtLeastInternal("Tiramisu")).isFalse();
+ assertThat(sdkLevel.isAtLeastInternal("U")).isFalse();
+
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtLeastInternal(""));
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtLeastInternal("current"));
assertThat(sdkLevel.isAtMostInternal("30")).isFalse();
assertThat(sdkLevel.isAtMostInternal("31")).isTrue();
assertThat(sdkLevel.isAtMostInternal("32")).isTrue();
- assertThrows(NumberFormatException.class, () -> sdkLevel.isAtMostInternal(""));
- assertThrows(NumberFormatException.class, () -> sdkLevel.isAtMostInternal("S"));
+ assertThat(sdkLevel.isAtMostInternal("R")).isTrue();
+ assertThat(sdkLevel.isAtMostInternal("S")).isTrue();
+ assertThat(sdkLevel.isAtMostInternal("Sv2")).isTrue();
+ assertThat(sdkLevel.isAtMostInternal("Tiramisu")).isTrue();
+ assertThat(sdkLevel.isAtMostInternal("U")).isTrue();
+
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtMostInternal(""));
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtMostInternal("current"));
+ }
+
+ @Test
+ public void testFinalizedSdk() {
+ // Test against finalized S / 31 release
+ UnboundedSdkLevel sdkLevel = new UnboundedSdkLevel(31, "REL", Set.of("Q", "R", "S"));
+
+ assertThat(sdkLevel.isAtLeastInternal("30")).isTrue();
+ assertThat(sdkLevel.isAtLeastInternal("31")).isTrue();
+ assertThat(sdkLevel.isAtLeastInternal("32")).isFalse();
+
+ // R and S must have been re-compiled and changed to integer after SDK finalization
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtLeastInternal("R"));
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtLeastInternal("S"));
+
+ // Future versions are S+
+ assertThat(sdkLevel.isAtLeastInternal("Sv2")).isFalse();
+ assertThat(sdkLevel.isAtLeastInternal("Tiramisu")).isFalse();
+ assertThat(sdkLevel.isAtLeastInternal("U")).isFalse();
+
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtLeastInternal(""));
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtLeastInternal("current"));
+
+ assertThat(sdkLevel.isAtMostInternal("30")).isFalse();
+ assertThat(sdkLevel.isAtMostInternal("31")).isTrue();
+ assertThat(sdkLevel.isAtMostInternal("32")).isTrue();
+
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtMostInternal("R"));
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtMostInternal("S"));
+ assertThat(sdkLevel.isAtMostInternal("Sv2")).isTrue();
+ assertThat(sdkLevel.isAtMostInternal("Tiramisu")).isTrue();
+ assertThat(sdkLevel.isAtMostInternal("U")).isTrue();
+
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtMostInternal(""));
+ assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtMostInternal("current"));
}
@Test
public void testNonFinalizedSdk_Sv2() {
- UnboundedSdkLevel sdkLevel = new UnboundedSdkLevel(31, "Sv2");
+ UnboundedSdkLevel sdkLevel = new UnboundedSdkLevel(31, "Sv2", Set.of("Q", "R", "S", "Sv2"));
+ assertThat(sdkLevel.isAtLeastInternal("30")).isTrue();
assertThat(sdkLevel.isAtLeastInternal("31")).isTrue();
assertThat(sdkLevel.isAtLeastInternal("32")).isFalse();
@@ -78,8 +132,10 @@
@Test
public void testNonFinalizedSdk_Tiramisu() {
- UnboundedSdkLevel sdkLevel = new UnboundedSdkLevel(31, "Tiramisu");
+ UnboundedSdkLevel sdkLevel = new UnboundedSdkLevel(31, "Tiramisu",
+ Set.of("Q", "R", "S", "Sv2", "Tiramisu"));
+ assertThat(sdkLevel.isAtLeastInternal("30")).isTrue();
assertThat(sdkLevel.isAtLeastInternal("31")).isTrue();
assertThat(sdkLevel.isAtLeastInternal("32")).isFalse();
@@ -106,4 +162,6 @@
assertThrows(IllegalArgumentException.class, () -> sdkLevel.isAtMostInternal("current"));
}
+
+
}