Merge "Abstract sdk_version string using sdkSpec type"
diff --git a/android/config.go b/android/config.go
index 54d47cf..bab3477 100644
--- a/android/config.go
+++ b/android/config.go
@@ -35,7 +35,8 @@
 
 var Bool = proptools.Bool
 var String = proptools.String
-var FutureApiLevel = 10000
+
+const FutureApiLevel = 10000
 
 // The configuration file name
 const configFileName = "soong.config"
diff --git a/java/aar.go b/java/aar.go
index 1ba65dc..d707e36 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -177,7 +177,10 @@
 	linkDeps = append(linkDeps, assetFiles...)
 
 	// SDK version flags
-	minSdkVersion := sdkVersionOrDefault(ctx, sdkContext.minSdkVersion())
+	minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersionString(ctx)
+	if err != nil {
+		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+	}
 
 	linkFlags = append(linkFlags, "--min-sdk-version "+minSdkVersion)
 	linkFlags = append(linkFlags, "--target-sdk-version "+minSdkVersion)
@@ -524,22 +527,22 @@
 	exportedStaticPackages android.Paths
 }
 
-func (a *AARImport) sdkVersion() string {
-	return String(a.properties.Sdk_version)
+func (a *AARImport) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(a.properties.Sdk_version))
 }
 
 func (a *AARImport) systemModules() string {
 	return ""
 }
 
-func (a *AARImport) minSdkVersion() string {
+func (a *AARImport) minSdkVersion() sdkSpec {
 	if a.properties.Min_sdk_version != nil {
-		return *a.properties.Min_sdk_version
+		return sdkSpecFrom(*a.properties.Min_sdk_version)
 	}
 	return a.sdkVersion()
 }
 
-func (a *AARImport) targetSdkVersion() string {
+func (a *AARImport) targetSdkVersion() sdkSpec {
 	return a.sdkVersion()
 }
 
diff --git a/java/android_manifest.go b/java/android_manifest.go
index dc7a3fc..e3646f5 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -59,7 +59,7 @@
 	if isLibrary {
 		args = append(args, "--library")
 	} else {
-		minSdkVersion, err := sdkVersionToNumber(ctx, sdkContext.minSdkVersion())
+		minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersion(ctx)
 		if err != nil {
 			ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
 		}
@@ -92,15 +92,28 @@
 	}
 
 	var deps android.Paths
-	targetSdkVersion := sdkVersionOrDefault(ctx, sdkContext.targetSdkVersion())
-	minSdkVersion := sdkVersionOrDefault(ctx, sdkContext.minSdkVersion())
-	if (UseApiFingerprint(ctx, sdkContext.targetSdkVersion()) ||
-		UseApiFingerprint(ctx, sdkContext.minSdkVersion())) {
-			apiFingerprint := ApiFingerprintPath(ctx)
-			deps = append(deps, apiFingerprint)
+	targetSdkVersion, err := sdkContext.targetSdkVersion().effectiveVersionString(ctx)
+	if err != nil {
+		ctx.ModuleErrorf("invalid targetSdkVersion: %s", err)
+	}
+	if UseApiFingerprint(ctx, targetSdkVersion) {
+		targetSdkVersion += fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
+		deps = append(deps, ApiFingerprintPath(ctx))
+	}
+
+	minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersionString(ctx)
+	if err != nil {
+		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+	}
+	if UseApiFingerprint(ctx, minSdkVersion) {
+		minSdkVersion += fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
+		deps = append(deps, ApiFingerprintPath(ctx))
 	}
 
 	fixedManifest := android.PathForModuleOut(ctx, "manifest_fixer", "AndroidManifest.xml")
+	if err != nil {
+		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        manifestFixerRule,
 		Description: "fix manifest",
diff --git a/java/androidmk.go b/java/androidmk.go
index 7c19180..f28ae10 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -93,7 +93,7 @@
 					if len(library.dexpreopter.builtInstalled) > 0 {
 						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", library.dexpreopter.builtInstalled)
 					}
-					entries.SetString("LOCAL_SDK_VERSION", library.sdkVersion())
+					entries.SetString("LOCAL_SDK_VERSION", library.sdkVersion().raw)
 					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", library.implementationAndResourcesJar)
 					entries.SetPath("LOCAL_SOONG_HEADER_JAR", library.headerJarFile)
 
@@ -174,7 +174,7 @@
 				entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", !Bool(prebuilt.properties.Installable))
 				entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.combinedClasspathFile)
 				entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.combinedClasspathFile)
-				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion())
+				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion().raw)
 				entries.SetString("LOCAL_MODULE_STEM", prebuilt.Stem())
 			},
 		},
@@ -223,7 +223,7 @@
 				entries.SetPath("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", prebuilt.proguardFlags)
 				entries.SetPath("LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES", prebuilt.extraAaptPackagesFile)
 				entries.SetPath("LOCAL_FULL_MANIFEST_FILE", prebuilt.manifest)
-				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion())
+				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion().raw)
 			},
 		},
 	}}
diff --git a/java/app.go b/java/app.go
index c59047d..2ed3d4d 100755
--- a/java/app.go
+++ b/java/app.go
@@ -163,7 +163,7 @@
 func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) {
 	a.Module.deps(ctx)
 
-	if String(a.appProperties.Stl) == "c++_shared" && a.sdkVersion() == "" {
+	if String(a.appProperties.Stl) == "c++_shared" && !a.sdkVersion().specified() {
 		ctx.PropertyErrorf("stl", "sdk_version must be set in order to use c++_shared")
 	}
 
@@ -212,7 +212,7 @@
 // Returns true if the native libraries should be stored in the APK uncompressed and the
 // extractNativeLibs application flag should be set to false in the manifest.
 func (a *AndroidApp) useEmbeddedNativeLibs(ctx android.ModuleContext) bool {
-	minSdkVersion, err := sdkVersionToNumber(ctx, a.minSdkVersion())
+	minSdkVersion, err := a.minSdkVersion().effectiveVersion(ctx)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "invalid value %q: %s", a.minSdkVersion(), err)
 	}
@@ -1271,22 +1271,22 @@
 	ctx.InstallFile(r.installDir, r.outputFile.Base(), r.outputFile)
 }
 
-func (r *RuntimeResourceOverlay) sdkVersion() string {
-	return String(r.properties.Sdk_version)
+func (r *RuntimeResourceOverlay) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(r.properties.Sdk_version))
 }
 
 func (r *RuntimeResourceOverlay) systemModules() string {
 	return ""
 }
 
-func (r *RuntimeResourceOverlay) minSdkVersion() string {
+func (r *RuntimeResourceOverlay) minSdkVersion() sdkSpec {
 	if r.properties.Min_sdk_version != nil {
-		return *r.properties.Min_sdk_version
+		return sdkSpecFrom(*r.properties.Min_sdk_version)
 	}
 	return r.sdkVersion()
 }
 
-func (r *RuntimeResourceOverlay) targetSdkVersion() string {
+func (r *RuntimeResourceOverlay) targetSdkVersion() sdkSpec {
 	return r.sdkVersion()
 }
 
diff --git a/java/dex.go b/java/dex.go
index cd6d90d..ed4dfcf 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -73,12 +73,12 @@
 			"--verbose")
 	}
 
-	minSdkVersion, err := sdkVersionToNumberAsString(ctx, j.minSdkVersion())
+	minSdkVersion, err := j.minSdkVersion().effectiveVersion(ctx)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "%s", err)
 	}
 
-	flags = append(flags, "--min-api "+minSdkVersion)
+	flags = append(flags, "--min-api "+minSdkVersion.asNumberString())
 	return flags
 }
 
diff --git a/java/droiddoc.go b/java/droiddoc.go
index a10ec81..abdceba 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -424,19 +424,19 @@
 
 var _ android.OutputFileProducer = (*Javadoc)(nil)
 
-func (j *Javadoc) sdkVersion() string {
-	return String(j.properties.Sdk_version)
+func (j *Javadoc) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(j.properties.Sdk_version))
 }
 
 func (j *Javadoc) systemModules() string {
 	return proptools.String(j.properties.System_modules)
 }
 
-func (j *Javadoc) minSdkVersion() string {
+func (j *Javadoc) minSdkVersion() sdkSpec {
 	return j.sdkVersion()
 }
 
-func (j *Javadoc) targetSdkVersion() string {
+func (j *Javadoc) targetSdkVersion() sdkSpec {
 	return j.sdkVersion()
 }
 
diff --git a/java/java.go b/java/java.go
index 4c6a5a5..794ee68 100644
--- a/java/java.go
+++ b/java/java.go
@@ -87,7 +87,7 @@
 	if j.SocSpecific() || j.DeviceSpecific() ||
 		(j.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
 		if sc, ok := ctx.Module().(sdkContext); ok {
-			if sc.sdkVersion() == "" {
+			if !sc.sdkVersion().specified() {
 				ctx.PropertyErrorf("sdk_version",
 					"sdk_version must have a value when the module is located at vendor or product(only if PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE is set).")
 			}
@@ -98,12 +98,11 @@
 func (j *Module) checkPlatformAPI(ctx android.ModuleContext) {
 	if sc, ok := ctx.Module().(sdkContext); ok {
 		usePlatformAPI := proptools.Bool(j.deviceProperties.Platform_apis)
-		if usePlatformAPI != (sc.sdkVersion() == "") {
-			if usePlatformAPI {
-				ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.")
-			} else {
-				ctx.PropertyErrorf("platform_apis", "platform_apis must be true when sdk_version is empty.")
-			}
+		sdkVersionSpecified := sc.sdkVersion().specified()
+		if usePlatformAPI && sdkVersionSpecified {
+			ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.")
+		} else if !usePlatformAPI && !sdkVersionSpecified {
+			ctx.PropertyErrorf("platform_apis", "platform_apis must be true when sdk_version is empty.")
 		}
 
 	}
@@ -451,8 +450,8 @@
 }
 
 type SdkLibraryDependency interface {
-	SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths
-	SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths
+	SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths
+	SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths
 }
 
 type xref interface {
@@ -553,24 +552,24 @@
 			ctx.Config().UnbundledBuild())
 }
 
-func (j *Module) sdkVersion() string {
-	return String(j.deviceProperties.Sdk_version)
+func (j *Module) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(j.deviceProperties.Sdk_version))
 }
 
 func (j *Module) systemModules() string {
 	return proptools.String(j.deviceProperties.System_modules)
 }
 
-func (j *Module) minSdkVersion() string {
+func (j *Module) minSdkVersion() sdkSpec {
 	if j.deviceProperties.Min_sdk_version != nil {
-		return *j.deviceProperties.Min_sdk_version
+		return sdkSpecFrom(*j.deviceProperties.Min_sdk_version)
 	}
 	return j.sdkVersion()
 }
 
-func (j *Module) targetSdkVersion() string {
+func (j *Module) targetSdkVersion() sdkSpec {
 	if j.deviceProperties.Target_sdk_version != nil {
-		return *j.deviceProperties.Target_sdk_version
+		return sdkSpecFrom(*j.deviceProperties.Target_sdk_version)
 	}
 	return j.sdkVersion()
 }
@@ -776,26 +775,25 @@
 		name == "stub-annotations" || name == "private-stub-annotations-jar" ||
 		name == "core-lambda-stubs" || name == "core-generated-annotation-stubs":
 		return javaCore, true
-	case ver == "core_current":
+	case ver.kind == sdkCore:
 		return javaCore, false
 	case name == "android_system_stubs_current":
 		return javaSystem, true
-	case strings.HasPrefix(ver, "system_"):
+	case ver.kind == sdkSystem:
 		return javaSystem, false
 	case name == "android_test_stubs_current":
 		return javaSystem, true
-	case strings.HasPrefix(ver, "test_"):
+	case ver.kind == sdkTest:
 		return javaPlatform, false
 	case name == "android_stubs_current":
 		return javaSdk, true
-	case ver == "current":
+	case ver.kind == sdkPublic:
 		return javaSdk, false
-	case ver == "" || ver == "none" || ver == "core_platform":
+	case ver.kind == sdkPrivate || ver.kind == sdkNone || ver.kind == sdkCorePlatform:
 		return javaPlatform, false
+	case !ver.valid():
+		panic(fmt.Errorf("sdk_version is invalid. got %q", ver.raw))
 	default:
-		if _, err := strconv.Atoi(ver); err != nil {
-			panic(fmt.Errorf("expected sdk_version to be a number, got %q", ver))
-		}
 		return javaSdk, false
 	}
 }
@@ -992,19 +990,7 @@
 }
 
 func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext sdkContext) javaVersion {
-	v := sdkContext.sdkVersion()
-	// For PDK builds, use the latest SDK version instead of "current"
-	if ctx.Config().IsPdkBuild() &&
-		(v == "" || v == "none" || v == "core_platform" || v == "current") {
-		sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int)
-		latestSdkVersion := 0
-		if len(sdkVersions) > 0 {
-			latestSdkVersion = sdkVersions[len(sdkVersions)-1]
-		}
-		v = strconv.Itoa(latestSdkVersion)
-	}
-
-	sdk, err := sdkVersionToNumber(ctx, v)
+	sdk, err := sdkContext.sdkVersion().effectiveVersion(ctx)
 	if err != nil {
 		ctx.PropertyErrorf("sdk_version", "%s", err)
 	}
@@ -2282,11 +2268,11 @@
 	exportedSdkLibs       []string
 }
 
-func (j *Import) sdkVersion() string {
-	return String(j.properties.Sdk_version)
+func (j *Import) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(j.properties.Sdk_version))
 }
 
-func (j *Import) minSdkVersion() string {
+func (j *Import) minSdkVersion() sdkSpec {
 	return j.sdkVersion()
 }
 
diff --git a/java/sdk.go b/java/sdk.go
index 2dbcf4a..f388358 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -38,14 +38,16 @@
 var apiFingerprintPathKey = android.NewOnceKey("apiFingerprintPathKey")
 
 type sdkContext interface {
-	// sdkVersion returns the sdk_version property of the current module, or an empty string if it is not set.
-	sdkVersion() string
+	// sdkVersion returns sdkSpec that corresponds to the sdk_version property of the current module
+	sdkVersion() sdkSpec
 	// systemModules returns the system_modules property of the current module, or an empty string if it is not set.
 	systemModules() string
-	// minSdkVersion returns the min_sdk_version property of the current module, or sdkVersion() if it is not set.
-	minSdkVersion() string
-	// targetSdkVersion returns the target_sdk_version property of the current module, or sdkVersion() if it is not set.
-	targetSdkVersion() string
+	// minSdkVersion returns sdkSpec that corresponds to the min_sdk_version property of the current module,
+	// or from sdk_version if it is not set.
+	minSdkVersion() sdkSpec
+	// targetSdkVersion returns the sdkSpec that corresponds to the target_sdk_version property of the current module,
+	// or from sdk_version if it is not set.
+	targetSdkVersion() sdkSpec
 }
 
 func UseApiFingerprint(ctx android.BaseModuleContext, v string) bool {
@@ -58,79 +60,236 @@
 	return false
 }
 
-func sdkVersionOrDefault(ctx android.BaseModuleContext, v string) string {
-	var sdkVersion string
-	switch v {
-	case "", "none", "current", "test_current", "system_current", "core_current", "core_platform":
-		sdkVersion = ctx.Config().DefaultAppTargetSdk()
+// sdkKind represents a particular category of an SDK spec like public, system, test, etc.
+type sdkKind int
+
+const (
+	sdkInvalid sdkKind = iota
+	sdkNone
+	sdkCore
+	sdkCorePlatform
+	sdkPublic
+	sdkSystem
+	sdkTest
+	sdkPrivate
+)
+
+// String returns the string representation of this sdkKind
+func (k sdkKind) String() string {
+	switch k {
+	case sdkPrivate:
+		return "private"
+	case sdkNone:
+		return "none"
+	case sdkPublic:
+		return "public"
+	case sdkSystem:
+		return "system"
+	case sdkTest:
+		return "test"
+	case sdkCore:
+		return "core"
+	case sdkCorePlatform:
+		return "core_platform"
 	default:
-		sdkVersion = v
+		return "invalid"
 	}
-	if UseApiFingerprint(ctx, sdkVersion) {
-		apiFingerprint := ApiFingerprintPath(ctx)
-		sdkVersion += fmt.Sprintf(".$$(cat %s)", apiFingerprint.String())
-	}
-	return sdkVersion
 }
 
-// Returns a sdk version as a number.  For modules targeting an unreleased SDK (meaning it does not yet have a number)
-// it returns android.FutureApiLevel (10000).
-func sdkVersionToNumber(ctx android.EarlyModuleContext, v string) (int, error) {
-	switch v {
-	case "", "none", "current", "test_current", "system_current", "core_current", "core_platform":
-		return ctx.Config().DefaultAppTargetSdkInt(), nil
-	default:
-		n := android.GetNumericSdkVersion(v)
-		if i, err := strconv.Atoi(n); err != nil {
-			return -1, fmt.Errorf("invalid sdk version %q", n)
-		} else {
-			return i, nil
+// sdkVersion represents a specific version number of an SDK spec of a particular kind
+type sdkVersion int
+
+const (
+	// special version number for a not-yet-frozen SDK
+	sdkVersionCurrent sdkVersion = sdkVersion(android.FutureApiLevel)
+	// special version number to be used for SDK specs where version number doesn't
+	// make sense, e.g. "none", "", etc.
+	sdkVersionNone sdkVersion = sdkVersion(0)
+)
+
+// isCurrent checks if the sdkVersion refers to the not-yet-published version of an sdkKind
+func (v sdkVersion) isCurrent() bool {
+	return v == sdkVersionCurrent
+}
+
+// isNumbered checks if the sdkVersion refers to the published (a.k.a numbered) version of an sdkKind
+func (v sdkVersion) isNumbered() bool {
+	return !v.isCurrent() && v != sdkVersionNone
+}
+
+// String returns the string representation of this sdkVersion.
+func (v sdkVersion) String() string {
+	if v.isCurrent() {
+		return "current"
+	} else if v.isNumbered() {
+		return strconv.Itoa(int(v))
+	}
+	return "(no version)"
+}
+
+// asNumberString directly converts the numeric value of this sdk version as a string.
+// When isNumbered() is true, this method is the same as String(). However, for sdkVersionCurrent
+// and sdkVersionNone, this returns 10000 and 0 while String() returns "current" and "(no version"),
+// respectively.
+func (v sdkVersion) asNumberString() string {
+	return strconv.Itoa(int(v))
+}
+
+// sdkSpec represents the kind and the version of an SDK for a module to build against
+type sdkSpec struct {
+	kind    sdkKind
+	version sdkVersion
+	raw     string
+}
+
+// valid checks if this sdkSpec is well-formed. Note however that true doesn't mean that the
+// specified SDK actually exists.
+func (s sdkSpec) valid() bool {
+	return s.kind != sdkInvalid
+}
+
+// specified checks if this sdkSpec is well-formed and is not "".
+func (s sdkSpec) specified() bool {
+	return s.valid() && s.kind != sdkPrivate
+}
+
+// prebuiltSdkAvailableForUnbundledBuilt tells whether this sdkSpec can have a prebuilt SDK
+// that can be used for unbundled builds.
+func (s sdkSpec) prebuiltSdkAvailableForUnbundledBuild() bool {
+	// "", "none", and "core_platform" are not available for unbundled build
+	// as we don't/can't have prebuilt stub for the versions
+	return s.kind != sdkPrivate && s.kind != sdkNone && s.kind != sdkCorePlatform
+}
+
+// forPdkBuild converts this sdkSpec into another sdkSpec that is for the PDK builds.
+func (s sdkSpec) forPdkBuild(ctx android.EarlyModuleContext) sdkSpec {
+	// For PDK builds, use the latest SDK version instead of "current" or ""
+	if s.kind == sdkPrivate || s.kind == sdkPublic {
+		kind := s.kind
+		if kind == sdkPrivate {
+			// We don't have prebuilt SDK for private APIs, so use the public SDK
+			// instead. This looks odd, but that's how it has been done.
+			// TODO(b/148271073): investigate the need for this.
+			kind = sdkPublic
 		}
+		version := sdkVersion(LatestSdkVersionInt(ctx))
+		return sdkSpec{kind, version, s.raw}
 	}
+	return s
 }
 
-func sdkVersionToNumberAsString(ctx android.EarlyModuleContext, v string) (string, error) {
-	n, err := sdkVersionToNumber(ctx, v)
-	if err != nil {
-		return "", err
+// usePrebuilt determines whether prebuilt SDK should be used for this sdkSpec with the given context.
+func (s sdkSpec) usePrebuilt(ctx android.EarlyModuleContext) bool {
+	if s.version.isCurrent() {
+		// "current" can be built from source and be from prebuilt SDK
+		return ctx.Config().UnbundledBuildUsePrebuiltSdks()
+	} else if s.version.isNumbered() {
+		// sanity check
+		if s.kind != sdkPublic && s.kind != sdkSystem && s.kind != sdkTest {
+			panic(fmt.Errorf("prebuilt SDK is not not available for sdkKind=%q", s.kind))
+			return false
+		}
+		// numbered SDKs are always from prebuilt
+		return true
 	}
-	return strconv.Itoa(n), nil
+	// "", "none", "core_platform" fall here
+	return false
+}
+
+// effectiveVersion converts an sdkSpec into the concrete sdkVersion that the module
+// should use. For modules targeting an unreleased SDK (meaning it does not yet have a number)
+// it returns android.FutureApiLevel(10000).
+func (s sdkSpec) effectiveVersion(ctx android.EarlyModuleContext) (sdkVersion, error) {
+	if !s.valid() {
+		return s.version, fmt.Errorf("invalid sdk version %q", s.raw)
+	}
+	if ctx.Config().IsPdkBuild() {
+		s = s.forPdkBuild(ctx)
+	}
+	if s.version.isNumbered() {
+		return s.version, nil
+	}
+	return sdkVersion(ctx.Config().DefaultAppTargetSdkInt()), nil
+}
+
+// effectiveVersionString converts an sdkSpec into the concrete version string that the module
+// should use. For modules targeting an unreleased SDK (meaning it does not yet have a number)
+// it returns the codename (P, Q, R, etc.)
+func (s sdkSpec) effectiveVersionString(ctx android.EarlyModuleContext) (string, error) {
+	ver, err := s.effectiveVersion(ctx)
+	if err == nil && int(ver) == ctx.Config().DefaultAppTargetSdkInt() {
+		return ctx.Config().DefaultAppTargetSdk(), nil
+	}
+	return ver.String(), err
+}
+
+func sdkSpecFrom(str string) sdkSpec {
+	switch str {
+	// special cases first
+	case "":
+		return sdkSpec{sdkPrivate, sdkVersionNone, str}
+	case "none":
+		return sdkSpec{sdkNone, sdkVersionNone, str}
+	case "core_platform":
+		return sdkSpec{sdkCorePlatform, sdkVersionNone, str}
+	default:
+		// the syntax is [kind_]version
+		sep := strings.LastIndex(str, "_")
+
+		var kindString string
+		if sep == 0 {
+			return sdkSpec{sdkInvalid, sdkVersionNone, str}
+		} else if sep == -1 {
+			kindString = ""
+		} else {
+			kindString = str[0:sep]
+		}
+		versionString := str[sep+1 : len(str)]
+
+		var kind sdkKind
+		switch kindString {
+		case "":
+			kind = sdkPublic
+		case "core":
+			kind = sdkCore
+		case "system":
+			kind = sdkSystem
+		case "test":
+			kind = sdkTest
+		default:
+			return sdkSpec{sdkInvalid, sdkVersionNone, str}
+		}
+
+		var version sdkVersion
+		if versionString == "current" {
+			version = sdkVersionCurrent
+		} else if i, err := strconv.Atoi(versionString); err == nil {
+			version = sdkVersion(i)
+		} else {
+			return sdkSpec{sdkInvalid, sdkVersionNone, str}
+		}
+
+		return sdkSpec{kind, version, str}
+	}
 }
 
 func decodeSdkDep(ctx android.EarlyModuleContext, sdkContext sdkContext) sdkDep {
-	v := sdkContext.sdkVersion()
-
-	// For PDK builds, use the latest SDK version instead of "current"
-	if ctx.Config().IsPdkBuild() && (v == "" || v == "current") {
-		sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int)
-		latestSdkVersion := 0
-		if len(sdkVersions) > 0 {
-			latestSdkVersion = sdkVersions[len(sdkVersions)-1]
-		}
-		v = strconv.Itoa(latestSdkVersion)
-	}
-
-	numericSdkVersion, err := sdkVersionToNumber(ctx, v)
-	if err != nil {
-		ctx.PropertyErrorf("sdk_version", "%s", err)
+	sdkVersion := sdkContext.sdkVersion()
+	if !sdkVersion.valid() {
+		ctx.PropertyErrorf("sdk_version", "invalid version %q", sdkVersion.raw)
 		return sdkDep{}
 	}
 
-	toPrebuilt := func(sdk string) sdkDep {
-		var api, v string
-		if strings.Contains(sdk, "_") {
-			t := strings.Split(sdk, "_")
-			api = t[0]
-			v = t[1]
-		} else {
-			api = "public"
-			v = sdk
-		}
-		dir := filepath.Join("prebuilts", "sdk", v, api)
+	if ctx.Config().IsPdkBuild() {
+		sdkVersion = sdkVersion.forPdkBuild(ctx)
+	}
+
+	if sdkVersion.usePrebuilt(ctx) {
+		dir := filepath.Join("prebuilts", "sdk", sdkVersion.version.String(), sdkVersion.kind.String())
 		jar := filepath.Join(dir, "android.jar")
 		// There's no aidl for other SDKs yet.
 		// TODO(77525052): Add aidl files for other SDKs too.
-		public_dir := filepath.Join("prebuilts", "sdk", v, "public")
+		public_dir := filepath.Join("prebuilts", "sdk", sdkVersion.version.String(), "public")
 		aidl := filepath.Join(public_dir, "framework.aidl")
 		jarPath := android.ExistentPathForSource(ctx, jar)
 		aidlPath := android.ExistentPathForSource(ctx, aidl)
@@ -139,17 +298,17 @@
 		if (!jarPath.Valid() || !aidlPath.Valid()) && ctx.Config().AllowMissingDependencies() {
 			return sdkDep{
 				invalidVersion: true,
-				bootclasspath:  []string{fmt.Sprintf("sdk_%s_%s_android", api, v)},
+				bootclasspath:  []string{fmt.Sprintf("sdk_%s_%s_android", sdkVersion.kind, sdkVersion.version.String())},
 			}
 		}
 
 		if !jarPath.Valid() {
-			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdk, jar)
+			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.raw, jar)
 			return sdkDep{}
 		}
 
 		if !aidlPath.Valid() {
-			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdk, aidl)
+			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.raw, aidl)
 			return sdkDep{}
 		}
 
@@ -173,31 +332,26 @@
 
 	// Ensures that the specificed system SDK version is one of BOARD_SYSTEMSDK_VERSIONS (for vendor apks)
 	// or PRODUCT_SYSTEMSDK_VERSIONS (for other apks or when BOARD_SYSTEMSDK_VERSIONS is not set)
-	if strings.HasPrefix(v, "system_") && numericSdkVersion != android.FutureApiLevel {
+	if sdkVersion.kind == sdkSystem && sdkVersion.version.isNumbered() {
 		allowed_versions := ctx.DeviceConfig().PlatformSystemSdkVersions()
 		if ctx.DeviceSpecific() || ctx.SocSpecific() {
 			if len(ctx.DeviceConfig().SystemSdkVersions()) > 0 {
 				allowed_versions = ctx.DeviceConfig().SystemSdkVersions()
 			}
 		}
-		if len(allowed_versions) > 0 && !android.InList(strconv.Itoa(numericSdkVersion), allowed_versions) {
+		if len(allowed_versions) > 0 && !android.InList(sdkVersion.version.String(), allowed_versions) {
 			ctx.PropertyErrorf("sdk_version", "incompatible sdk version %q. System SDK version should be one of %q",
-				v, allowed_versions)
+				sdkVersion.raw, allowed_versions)
 		}
 	}
 
-	if ctx.Config().UnbundledBuildUsePrebuiltSdks() &&
-		v != "" && v != "none" && v != "core_platform" {
-		return toPrebuilt(v)
-	}
-
-	switch v {
-	case "":
+	switch sdkVersion.kind {
+	case sdkPrivate:
 		return sdkDep{
 			useDefaultLibs:     true,
 			frameworkResModule: "framework-res",
 		}
-	case "none":
+	case sdkNone:
 		systemModules := sdkContext.systemModules()
 		if systemModules == "" {
 			ctx.PropertyErrorf("sdk_version",
@@ -214,22 +368,22 @@
 			systemModules:  systemModules,
 			bootclasspath:  []string{systemModules},
 		}
-	case "core_platform":
+	case sdkCorePlatform:
 		return sdkDep{
 			useDefaultLibs:     true,
 			frameworkResModule: "framework-res",
 			noFrameworksLibs:   true,
 		}
-	case "current":
+	case sdkPublic:
 		return toModule("android_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx))
-	case "system_current":
+	case sdkSystem:
 		return toModule("android_system_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx))
-	case "test_current":
+	case sdkTest:
 		return toModule("android_test_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx))
-	case "core_current":
+	case sdkCore:
 		return toModule("core.current.stubs", "", nil)
 	default:
-		return toPrebuilt(v)
+		panic(fmt.Errorf("invalid sdk %q", sdkVersion.raw))
 	}
 }
 
@@ -262,6 +416,15 @@
 	ctx.Config().Once(sdkVersionsKey, func() interface{} { return sdkVersions })
 }
 
+func LatestSdkVersionInt(ctx android.EarlyModuleContext) int {
+	sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int)
+	latestSdkVersion := 0
+	if len(sdkVersions) > 0 {
+		latestSdkVersion = sdkVersions[len(sdkVersions)-1]
+	}
+	return latestSdkVersion
+}
+
 func sdkSingletonFactory() android.Singleton {
 	return sdkSingleton{}
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 72c4a7c..530e02c 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -650,27 +650,27 @@
 	mctx.CreateModule(android.PrebuiltEtcFactory, &etcProps)
 }
 
-func (module *SdkLibrary) PrebuiltJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
-	var api, v string
-	if sdkVersion == "" || sdkVersion == "none" {
-		api = "system"
-		v = "current"
-	} else if strings.Contains(sdkVersion, "_") {
-		t := strings.Split(sdkVersion, "_")
-		api = t[0]
-		v = t[1]
+func (module *SdkLibrary) PrebuiltJars(ctx android.BaseModuleContext, s sdkSpec) android.Paths {
+	var ver sdkVersion
+	var kind sdkKind
+	if s.usePrebuilt(ctx) {
+		ver = s.version
+		kind = s.kind
 	} else {
-		api = "public"
-		v = sdkVersion
+		// We don't have prebuilt SDK for the specific sdkVersion.
+		// Instead of breaking the build, fallback to use "system_current"
+		ver = sdkVersionCurrent
+		kind = sdkSystem
 	}
-	dir := filepath.Join("prebuilts", "sdk", v, api)
+
+	dir := filepath.Join("prebuilts", "sdk", ver.String(), kind.String())
 	jar := filepath.Join(dir, module.BaseModuleName()+".jar")
 	jarPath := android.ExistentPathForSource(ctx, jar)
 	if !jarPath.Valid() {
 		if ctx.Config().AllowMissingDependencies() {
 			return android.Paths{android.PathForSource(ctx, jar)}
 		} else {
-			ctx.PropertyErrorf("sdk_library", "invalid sdk version %q, %q does not exist", sdkVersion, jar)
+			ctx.PropertyErrorf("sdk_library", "invalid sdk version %q, %q does not exist", s.raw, jar)
 		}
 		return nil
 	}
@@ -678,32 +678,34 @@
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
+func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
 	// This module is just a wrapper for the stubs.
 	if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
 		return module.PrebuiltJars(ctx, sdkVersion)
 	} else {
-		if strings.HasPrefix(sdkVersion, "system_") {
+		switch sdkVersion.kind {
+		case sdkSystem:
 			return module.systemApiStubsPath
-		} else if sdkVersion == "" {
+		case sdkPrivate:
 			return module.Library.HeaderJars()
-		} else {
+		default:
 			return module.publicApiStubsPath
 		}
 	}
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
+func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
 	// This module is just a wrapper for the stubs.
 	if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
 		return module.PrebuiltJars(ctx, sdkVersion)
 	} else {
-		if strings.HasPrefix(sdkVersion, "system_") {
+		switch sdkVersion.kind {
+		case sdkSystem:
 			return module.systemApiStubsImplPath
-		} else if sdkVersion == "" {
+		case sdkPrivate:
 			return module.Library.ImplementationJars()
-		} else {
+		default:
 			return module.publicApiStubsImplPath
 		}
 	}
@@ -923,13 +925,13 @@
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *sdkLibraryImport) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
+func (module *sdkLibraryImport) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
 	// This module is just a wrapper for the prebuilt stubs.
 	return module.stubsPath
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *sdkLibraryImport) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
+func (module *sdkLibraryImport) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
 	// This module is just a wrapper for the stubs.
 	return module.stubsPath
 }