Merge "Replace stringly-typed API levels."
diff --git a/android/api_levels.go b/android/api_levels.go
index 0872066..e5405ca 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -24,6 +24,192 @@
 	RegisterSingletonType("api_levels", ApiLevelsSingleton)
 }
 
+// An API level, which may be a finalized (numbered) API, a preview (codenamed)
+// API, or the future API level (10000). Can be parsed from a string with
+// ApiLevelFromUser or ApiLevelOrPanic.
+//
+// The different *types* of API levels are handled separately. Currently only
+// Java has these, and they're managed with the sdkKind enum of the sdkSpec. A
+// future cleanup should be to migrate sdkSpec to using ApiLevel instead of its
+// sdkVersion int, and to move sdkSpec into this package.
+type ApiLevel struct {
+	// The string representation of the API level.
+	value string
+
+	// A number associated with the API level. The exact value depends on
+	// whether this API level is a preview or final API.
+	//
+	// For final API levels, this is the assigned version number.
+	//
+	// For preview API levels, this value has no meaning except to index known
+	// previews to determine ordering.
+	number int
+
+	// Identifies this API level as either a preview or final API level.
+	isPreview bool
+}
+
+// Returns the canonical name for this API level. For a finalized API level
+// this will be the API number as a string. For a preview API level this
+// will be the codename, or "current".
+func (this ApiLevel) String() string {
+	return this.value
+}
+
+// Returns true if this is a non-final API level.
+func (this ApiLevel) IsPreview() bool {
+	return this.isPreview
+}
+
+// Returns true if this is the unfinalized "current" API level. This means
+// different things across Java and native. Java APIs do not use explicit
+// codenames, so all non-final codenames are grouped into "current". For native
+// explicit codenames are typically used, and current is the union of all
+// non-final APIs, including those that may not yet be in any codename.
+//
+// Note that in a build where the platform is final, "current" will not be a
+// preview API level but will instead be canonicalized to the final API level.
+func (this ApiLevel) IsCurrent() bool {
+	return this.value == "current"
+}
+
+// Returns -1 if the current API level is less than the argument, 0 if they
+// are equal, and 1 if it is greater than the argument.
+func (this ApiLevel) CompareTo(other ApiLevel) int {
+	if this.IsPreview() && !other.IsPreview() {
+		return 1
+	} else if !this.IsPreview() && other.IsPreview() {
+		return -1
+	}
+
+	if this.number < other.number {
+		return -1
+	} else if this.number == other.number {
+		return 0
+	} else {
+		return 1
+	}
+}
+
+func (this ApiLevel) EqualTo(other ApiLevel) bool {
+	return this.CompareTo(other) == 0
+}
+
+func (this ApiLevel) GreaterThan(other ApiLevel) bool {
+	return this.CompareTo(other) > 0
+}
+
+func (this ApiLevel) GreaterThanOrEqualTo(other ApiLevel) bool {
+	return this.CompareTo(other) >= 0
+}
+
+func (this ApiLevel) LessThan(other ApiLevel) bool {
+	return this.CompareTo(other) < 0
+}
+
+func (this ApiLevel) LessThanOrEqualTo(other ApiLevel) bool {
+	return this.CompareTo(other) <= 0
+}
+
+func uncheckedFinalApiLevel(num int) ApiLevel {
+	return ApiLevel{
+		value:     strconv.Itoa(num),
+		number:    num,
+		isPreview: false,
+	}
+}
+
+// TODO: Merge with FutureApiLevel
+var CurrentApiLevel = ApiLevel{
+	value:     "current",
+	number:    10000,
+	isPreview: true,
+}
+
+var NoneApiLevel = ApiLevel{
+	value: "(no version)",
+	// Not 0 because we don't want this to compare equal with the first preview.
+	number:    -1,
+	isPreview: true,
+}
+
+// The first version that introduced 64-bit ABIs.
+var FirstLp64Version = uncheckedFinalApiLevel(21)
+
+// The first API level that does not require NDK code to link
+// libandroid_support.
+var FirstNonLibAndroidSupportVersion = uncheckedFinalApiLevel(21)
+
+// If the `raw` input is the codename of an API level has been finalized, this
+// function returns the API level number associated with that API level. If the
+// input is *not* a finalized codename, the input is returned unmodified.
+//
+// For example, at the time of writing, R has been finalized as API level 30,
+// but S is in development so it has no number assigned. For the following
+// inputs:
+//
+// * "30" -> "30"
+// * "R" -> "30"
+// * "S" -> "S"
+func ReplaceFinalizedCodenames(ctx EarlyModuleContext, raw string) string {
+	num, ok := getFinalCodenamesMap(ctx.Config())[raw]
+	if !ok {
+		return raw
+	}
+
+	return strconv.Itoa(num)
+}
+
+// Converts the given string `raw` to an ApiLevel, possibly returning an error.
+//
+// `raw` must be non-empty. Passing an empty string results in a panic.
+//
+// "current" will return CurrentApiLevel, which is the ApiLevel associated with
+// an arbitrary future release (often referred to as API level 10000).
+//
+// Finalized codenames will be interpreted as their final API levels, not the
+// preview of the associated releases. R is now API 30, not the R preview.
+//
+// Future codenames return a preview API level that has no associated integer.
+//
+// Inputs that are not "current", known previews, or convertible to an integer
+// will return an error.
+func ApiLevelFromUser(ctx EarlyModuleContext, raw string) (ApiLevel, error) {
+	if raw == "" {
+		panic("API level string must be non-empty")
+	}
+
+	if raw == "current" {
+		return CurrentApiLevel, nil
+	}
+
+	for _, preview := range ctx.Config().PreviewApiLevels() {
+		if raw == preview.String() {
+			return preview, nil
+		}
+	}
+
+	canonical := ReplaceFinalizedCodenames(ctx, raw)
+	asInt, err := strconv.Atoi(canonical)
+	if err != nil {
+		return NoneApiLevel, fmt.Errorf("%q could not be parsed as an integer and is not a recognized codename", canonical)
+	}
+
+	apiLevel := uncheckedFinalApiLevel(asInt)
+	return apiLevel, nil
+}
+
+// Converts an API level string `raw` into an ApiLevel in the same method as
+// `ApiLevelFromUser`, but the input is assumed to have no errors and any errors
+// will panic instead of returning an error.
+func ApiLevelOrPanic(ctx EarlyModuleContext, raw string) ApiLevel {
+	value, err := ApiLevelFromUser(ctx, raw)
+	if err != nil {
+		panic(err.Error())
+	}
+	return value
+}
+
 func ApiLevelsSingleton() Singleton {
 	return &apiLevelsSingleton{}
 }
@@ -52,6 +238,36 @@
 	return PathForOutput(ctx, "api_levels.json")
 }
 
+var finalCodenamesMapKey = NewOnceKey("FinalCodenamesMap")
+
+func getFinalCodenamesMap(config Config) map[string]int {
+	return config.Once(finalCodenamesMapKey, func() interface{} {
+		apiLevelsMap := map[string]int{
+			"G":     9,
+			"I":     14,
+			"J":     16,
+			"J-MR1": 17,
+			"J-MR2": 18,
+			"K":     19,
+			"L":     21,
+			"L-MR1": 22,
+			"M":     23,
+			"N":     24,
+			"N-MR1": 25,
+			"O":     26,
+			"O-MR1": 27,
+			"P":     28,
+			"Q":     29,
+		}
+
+		if Bool(config.productVariables.Platform_sdk_final) {
+			apiLevelsMap["current"] = config.PlatformSdkVersionInt()
+		}
+
+		return apiLevelsMap
+	}).(map[string]int)
+}
+
 var apiLevelsMapKey = NewOnceKey("ApiLevelsMap")
 
 func getApiLevelsMap(config Config) map[string]int {
diff --git a/android/config.go b/android/config.go
index dd622e5..1c06e8c 100644
--- a/android/config.go
+++ b/android/config.go
@@ -642,8 +642,34 @@
 	return String(c.productVariables.Platform_base_os)
 }
 
-func (c *config) MinSupportedSdkVersion() int {
-	return 16
+func (c *config) MinSupportedSdkVersion() ApiLevel {
+	return uncheckedFinalApiLevel(16)
+}
+
+func (c *config) FinalApiLevels() []ApiLevel {
+	var levels []ApiLevel
+	for i := 1; i <= c.PlatformSdkVersionInt(); i++ {
+		levels = append(levels, uncheckedFinalApiLevel(i))
+	}
+	return levels
+}
+
+func (c *config) PreviewApiLevels() []ApiLevel {
+	var levels []ApiLevel
+	for i, codename := range c.PlatformVersionActiveCodenames() {
+		levels = append(levels, ApiLevel{
+			value:     codename,
+			number:    i,
+			isPreview: true,
+		})
+	}
+	return levels
+}
+
+func (c *config) AllSupportedApiLevels() []ApiLevel {
+	var levels []ApiLevel
+	levels = append(levels, c.FinalApiLevels()...)
+	return append(levels, c.PreviewApiLevels()...)
 }
 
 func (c *config) DefaultAppTargetSdkInt() int {
diff --git a/android/makevars.go b/android/makevars.go
index 003a9df..374986e 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -18,7 +18,6 @@
 	"bytes"
 	"fmt"
 	"sort"
-	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -31,7 +30,7 @@
 }
 
 func androidMakeVarsProvider(ctx MakeVarsContext) {
-	ctx.Strict("MIN_SUPPORTED_SDK_VERSION", strconv.Itoa(ctx.Config().MinSupportedSdkVersion()))
+	ctx.Strict("MIN_SUPPORTED_SDK_VERSION", ctx.Config().MinSupportedSdkVersion().String())
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/cc/Android.bp b/cc/Android.bp
index 831911e..ff2cdf3 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -13,6 +13,7 @@
     ],
     srcs: [
         "androidmk.go",
+        "api_level.go",
         "builder.go",
         "cc.go",
         "ccdeps.go",
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 380b4e9..5bdbac6 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -451,7 +451,7 @@
 }
 
 func (c *stubDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	entries.SubName = ndkLibrarySuffix + "." + c.properties.ApiLevel
+	entries.SubName = ndkLibrarySuffix + "." + c.apiLevel.String()
 	entries.Class = "SHARED_LIBRARIES"
 
 	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
diff --git a/cc/api_level.go b/cc/api_level.go
new file mode 100644
index 0000000..c93d6ed
--- /dev/null
+++ b/cc/api_level.go
@@ -0,0 +1,71 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// 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 cc
+
+import (
+	"fmt"
+
+	"android/soong/android"
+)
+
+func minApiForArch(ctx android.BaseModuleContext,
+	arch android.ArchType) android.ApiLevel {
+
+	switch arch {
+	case android.Arm, android.X86:
+		return ctx.Config().MinSupportedSdkVersion()
+	case android.Arm64, android.X86_64:
+		return android.FirstLp64Version
+	default:
+		panic(fmt.Errorf("Unknown arch %q", arch))
+	}
+}
+
+func nativeApiLevelFromUser(ctx android.BaseModuleContext,
+	raw string) (android.ApiLevel, error) {
+
+	min := minApiForArch(ctx, ctx.Arch().ArchType)
+	if raw == "minimum" {
+		return min, nil
+	}
+
+	value, err := android.ApiLevelFromUser(ctx, raw)
+	if err != nil {
+		return android.NoneApiLevel, err
+	}
+
+	if value.LessThan(min) {
+		return min, nil
+	}
+
+	return value, nil
+}
+
+func nativeApiLevelFromUserWithDefault(ctx android.BaseModuleContext,
+	raw string, defaultValue string) (android.ApiLevel, error) {
+	if raw == "" {
+		raw = defaultValue
+	}
+	return nativeApiLevelFromUser(ctx, raw)
+}
+
+func nativeApiLevelOrPanic(ctx android.BaseModuleContext,
+	raw string) android.ApiLevel {
+	value, err := nativeApiLevelFromUser(ctx, raw)
+	if err != nil {
+		panic(err.Error())
+	}
+	return value
+}
diff --git a/cc/cc.go b/cc/cc.go
index 70229be..b5a0261 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -629,7 +629,7 @@
 func (c *Module) ApiLevel() string {
 	if c.linker != nil {
 		if stub, ok := c.linker.(*stubDecorator); ok {
-			return stub.properties.ApiLevel
+			return stub.apiLevel.String()
 		}
 	}
 	panic(fmt.Errorf("ApiLevel() called on non-stub library module: %q", c.BaseModuleName()))
@@ -1682,11 +1682,13 @@
 		feature.begin(ctx)
 	}
 	if ctx.useSdk() && c.IsSdkVariant() {
-		version, err := normalizeNdkApiLevel(ctx, ctx.sdkVersion(), ctx.Arch())
+		version, err := nativeApiLevelFromUser(ctx, ctx.sdkVersion())
 		if err != nil {
 			ctx.PropertyErrorf("sdk_version", err.Error())
+			c.Properties.Sdk_version = nil
+		} else {
+			c.Properties.Sdk_version = StringPtr(version.String())
 		}
-		c.Properties.Sdk_version = StringPtr(version)
 	}
 }
 
@@ -3119,13 +3121,6 @@
 	return c.Properties.IsSdkVariant || c.AlwaysSdk()
 }
 
-func getCurrentNdkPrebuiltVersion(ctx DepsContext) string {
-	if ctx.Config().PlatformSdkVersionInt() > config.NdkMaxPrebuiltVersionInt {
-		return strconv.Itoa(config.NdkMaxPrebuiltVersionInt)
-	}
-	return ctx.Config().PlatformSdkVersion()
-}
-
 func kytheExtractAllFactory() android.Singleton {
 	return &kytheExtractAllSingleton{}
 }
diff --git a/cc/config/global.go b/cc/config/global.go
index 32f163d..b9f0332 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -126,8 +126,6 @@
 	ExperimentalCStdVersion   = "gnu11"
 	ExperimentalCppStdVersion = "gnu++2a"
 
-	NdkMaxPrebuiltVersionInt = 27
-
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
 	ClangDefaultVersion      = "clang-r383902b"
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index fe3efc0..4c6d98c 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"strconv"
 	"strings"
 	"sync"
 
@@ -52,6 +51,10 @@
 	ndkKnownLibsLock sync.Mutex
 )
 
+// The First_version and Unversioned_until properties of this struct should not
+// be used directly, but rather through the ApiLevel returning methods
+// firstVersion() and unversionedUntil().
+
 // Creates a stub shared library based on the provided version file.
 //
 // Example:
@@ -77,9 +80,7 @@
 	// https://github.com/android-ndk/ndk/issues/265.
 	Unversioned_until *string
 
-	// Private property for use by the mutator that splits per-API level. Can be
-	// one of <number:sdk_version> or <codename> or "current" passed to
-	// "ndkstubgen.py" as it is
+	// Use via apiLevel on the stubDecorator.
 	ApiLevel string `blueprint:"mutated"`
 
 	// True if this API is not yet ready to be shipped in the NDK. It will be
@@ -96,125 +97,33 @@
 	versionScriptPath     android.ModuleGenPath
 	parsedCoverageXmlPath android.ModuleOutPath
 	installPath           android.Path
+
+	apiLevel         android.ApiLevel
+	firstVersion     android.ApiLevel
+	unversionedUntil android.ApiLevel
 }
 
-// OMG GO
-func intMax(a int, b int) int {
-	if a > b {
-		return a
-	} else {
-		return b
-	}
-}
-
-func normalizeNdkApiLevel(ctx android.BaseModuleContext, apiLevel string,
-	arch android.Arch) (string, error) {
-
-	if apiLevel == "" {
-		panic("empty apiLevel not allowed")
-	}
-
-	if apiLevel == "current" {
-		return apiLevel, nil
-	}
-
-	minVersion := ctx.Config().MinSupportedSdkVersion()
-	firstArchVersions := map[android.ArchType]int{
-		android.Arm:    minVersion,
-		android.Arm64:  21,
-		android.X86:    minVersion,
-		android.X86_64: 21,
-	}
-
-	firstArchVersion, ok := firstArchVersions[arch.ArchType]
-	if !ok {
-		panic(fmt.Errorf("Arch %q not found in firstArchVersions", arch.ArchType))
-	}
-
-	if apiLevel == "minimum" {
-		return strconv.Itoa(firstArchVersion), nil
-	}
-
-	// If the NDK drops support for a platform version, we don't want to have to
-	// fix up every module that was using it as its SDK version. Clip to the
-	// supported version here instead.
-	version, err := strconv.Atoi(apiLevel)
-	if err != nil {
-		// Non-integer API levels are codenames.
-		return apiLevel, nil
-	}
-	version = intMax(version, minVersion)
-
-	return strconv.Itoa(intMax(version, firstArchVersion)), nil
-}
-
-func getFirstGeneratedVersion(firstSupportedVersion string, platformVersion int) (int, error) {
-	if firstSupportedVersion == "current" {
-		return platformVersion + 1, nil
-	}
-
-	return strconv.Atoi(firstSupportedVersion)
-}
-
-func shouldUseVersionScript(ctx android.BaseModuleContext, stub *stubDecorator) (bool, error) {
-	// unversioned_until is normally empty, in which case we should use the version script.
-	if String(stub.properties.Unversioned_until) == "" {
-		return true, nil
-	}
-
-	if String(stub.properties.Unversioned_until) == "current" {
-		if stub.properties.ApiLevel == "current" {
-			return true, nil
-		} else {
-			return false, nil
-		}
-	}
-
-	if stub.properties.ApiLevel == "current" {
-		return true, nil
-	}
-
-	unversionedUntil, err := android.ApiStrToNum(ctx, String(stub.properties.Unversioned_until))
-	if err != nil {
-		return true, err
-	}
-
-	version, err := android.ApiStrToNum(ctx, stub.properties.ApiLevel)
-	if err != nil {
-		return true, err
-	}
-
-	return version >= unversionedUntil, nil
+func shouldUseVersionScript(ctx BaseModuleContext, stub *stubDecorator) bool {
+	return stub.apiLevel.GreaterThanOrEqualTo(stub.unversionedUntil)
 }
 
 func generatePerApiVariants(ctx android.BottomUpMutatorContext, m *Module,
-	propName string, propValue string, perSplit func(*Module, string)) {
-	platformVersion := ctx.Config().PlatformSdkVersionInt()
+	from android.ApiLevel, perSplit func(*Module, android.ApiLevel)) {
 
-	firstSupportedVersion, err := normalizeNdkApiLevel(ctx, propValue,
-		ctx.Arch())
-	if err != nil {
-		ctx.PropertyErrorf(propName, err.Error())
+	var versions []android.ApiLevel
+	versionStrs := []string{}
+	for _, version := range ctx.Config().AllSupportedApiLevels() {
+		if version.GreaterThanOrEqualTo(from) {
+			versions = append(versions, version)
+			versionStrs = append(versionStrs, version.String())
+		}
 	}
-
-	firstGenVersion, err := getFirstGeneratedVersion(firstSupportedVersion,
-		platformVersion)
-	if err != nil {
-		// In theory this is impossible because we've already run this through
-		// normalizeNdkApiLevel above.
-		ctx.PropertyErrorf(propName, err.Error())
-	}
-
-	var versionStrs []string
-	for version := firstGenVersion; version <= platformVersion; version++ {
-		versionStrs = append(versionStrs, strconv.Itoa(version))
-	}
-	versionStrs = append(versionStrs, ctx.Config().PlatformVersionActiveCodenames()...)
-	versionStrs = append(versionStrs, "current")
+	versions = append(versions, android.CurrentApiLevel)
+	versionStrs = append(versionStrs, android.CurrentApiLevel.String())
 
 	modules := ctx.CreateVariations(versionStrs...)
 	for i, module := range modules {
-		perSplit(module.(*Module), versionStrs[i])
+		perSplit(module.(*Module), versions[i])
 	}
 }
 
@@ -228,25 +137,56 @@
 					ctx.Module().Disable()
 					return
 				}
-				generatePerApiVariants(ctx, m, "first_version",
-					String(compiler.properties.First_version),
-					func(m *Module, version string) {
+				firstVersion, err := nativeApiLevelFromUser(ctx,
+					String(compiler.properties.First_version))
+				if err != nil {
+					ctx.PropertyErrorf("first_version", err.Error())
+					return
+				}
+				generatePerApiVariants(ctx, m, firstVersion,
+					func(m *Module, version android.ApiLevel) {
 						m.compiler.(*stubDecorator).properties.ApiLevel =
-							version
+							version.String()
 					})
 			} else if m.SplitPerApiLevel() && m.IsSdkVariant() {
 				if ctx.Os() != android.Android {
 					return
 				}
-				generatePerApiVariants(ctx, m, "min_sdk_version",
-					m.MinSdkVersion(), func(m *Module, version string) {
-						m.Properties.Sdk_version = &version
+				from, err := nativeApiLevelFromUser(ctx, m.MinSdkVersion())
+				if err != nil {
+					ctx.PropertyErrorf("min_sdk_version", err.Error())
+					return
+				}
+				generatePerApiVariants(ctx, m, from,
+					func(m *Module, version android.ApiLevel) {
+						m.Properties.Sdk_version = StringPtr(version.String())
 					})
 			}
 		}
 	}
 }
 
+func (this *stubDecorator) initializeProperties(ctx BaseModuleContext) bool {
+	this.apiLevel = nativeApiLevelOrPanic(ctx, this.properties.ApiLevel)
+
+	var err error
+	this.firstVersion, err = nativeApiLevelFromUser(ctx,
+		String(this.properties.First_version))
+	if err != nil {
+		ctx.PropertyErrorf("first_version", err.Error())
+		return false
+	}
+
+	this.unversionedUntil, err = nativeApiLevelFromUserWithDefault(ctx,
+		String(this.properties.Unversioned_until), "minimum")
+	if err != nil {
+		ctx.PropertyErrorf("unversioned_until", err.Error())
+		return false
+	}
+
+	return true
+}
+
 func (c *stubDecorator) compilerInit(ctx BaseModuleContext) {
 	c.baseCompiler.compilerInit(ctx)
 
@@ -340,11 +280,16 @@
 		ctx.PropertyErrorf("symbol_file", "must end with .map.txt")
 	}
 
+	if !c.initializeProperties(ctx) {
+		// Emits its own errors, so we don't need to.
+		return Objects{}
+	}
+
 	symbolFile := String(c.properties.Symbol_file)
 	objs, versionScript := compileStubLibrary(ctx, flags, symbolFile,
-		c.properties.ApiLevel, "")
+		c.apiLevel.String(), "")
 	c.versionScriptPath = versionScript
-	if c.properties.ApiLevel == "current" && ctx.PrimaryArch() {
+	if c.apiLevel.IsCurrent() && ctx.PrimaryArch() {
 		c.parsedCoverageXmlPath = parseSymbolFileForCoverage(ctx, symbolFile)
 	}
 	return objs
@@ -366,12 +311,7 @@
 func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps,
 	objs Objects) android.Path {
 
-	useVersionScript, err := shouldUseVersionScript(ctx, stub)
-	if err != nil {
-		ctx.ModuleErrorf(err.Error())
-	}
-
-	if useVersionScript {
+	if shouldUseVersionScript(ctx, stub) {
 		linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String()
 		flags.Local.LdFlags = append(flags.Local.LdFlags, linkerScriptFlag)
 		flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath)
@@ -386,8 +326,6 @@
 
 func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) {
 	arch := ctx.Target().Arch.ArchType.Name
-	apiLevel := stub.properties.ApiLevel
-
 	// arm64 isn't actually a multilib toolchain, so unlike the other LP64
 	// architectures it's just installed to lib.
 	libDir := "lib"
@@ -396,7 +334,7 @@
 	}
 
 	installDir := getNdkInstallBase(ctx).Join(ctx, fmt.Sprintf(
-		"platforms/android-%s/arch-%s/usr/%s", apiLevel, arch, libDir))
+		"platforms/android-%s/arch-%s/usr/%s", stub.apiLevel, arch, libDir))
 	stub.installPath = ctx.InstallFile(installDir, path.Base(), path)
 }
 
diff --git a/cc/stl.go b/cc/stl.go
index e18fe95..406fa3a 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -17,7 +17,6 @@
 import (
 	"android/soong/android"
 	"fmt"
-	"strconv"
 )
 
 func getNdkStlFamily(m LinkableInterface) string {
@@ -136,23 +135,8 @@
 }
 
 func needsLibAndroidSupport(ctx BaseModuleContext) bool {
-	versionStr, err := normalizeNdkApiLevel(ctx, ctx.sdkVersion(), ctx.Arch())
-	if err != nil {
-		ctx.PropertyErrorf("sdk_version", err.Error())
-	}
-
-	if versionStr == "current" {
-		return false
-	}
-
-	version, err := strconv.Atoi(versionStr)
-	if err != nil {
-		panic(fmt.Sprintf(
-			"invalid API level returned from normalizeNdkApiLevel: %q",
-			versionStr))
-	}
-
-	return version < 21
+	version := nativeApiLevelOrPanic(ctx, ctx.sdkVersion())
+	return version.LessThan(android.FirstNonLibAndroidSupportVersion)
 }
 
 func staticUnwinder(ctx android.BaseModuleContext) string {