Relax apex package restriction for T+ jars

The ART AOT exemption only applies to Q/R/S, so module jars that have
min_sdk T+ do not need to follow the module package restriction, even if
they are part of a Q/R/S module (but not loaded on Q/R/S).

Relax the restriction to only apply to jars that have min_sdk before T.

Bug: 208773835
Test: m (runs apex tests)
Change-Id: I2c3ad8984ca05ad763bf6162bd478f93ab4ee650
diff --git a/android/neverallow.go b/android/neverallow.go
index b36bf04..0348619 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -252,7 +252,7 @@
 			continue
 		}
 
-		if !n.appliesToProperties(properties) {
+		if !n.appliesToProperties(ctx, properties) {
 			continue
 		}
 
@@ -272,8 +272,12 @@
 	}
 }
 
+type ValueMatcherContext interface {
+	Config() Config
+}
+
 type ValueMatcher interface {
-	Test(string) bool
+	Test(ValueMatcherContext, string) bool
 	String() string
 }
 
@@ -281,7 +285,7 @@
 	expected string
 }
 
-func (m *equalMatcher) Test(value string) bool {
+func (m *equalMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return m.expected == value
 }
 
@@ -292,7 +296,7 @@
 type anyMatcher struct {
 }
 
-func (m *anyMatcher) Test(value string) bool {
+func (m *anyMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return true
 }
 
@@ -306,7 +310,7 @@
 	prefix string
 }
 
-func (m *startsWithMatcher) Test(value string) bool {
+func (m *startsWithMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return strings.HasPrefix(value, m.prefix)
 }
 
@@ -318,7 +322,7 @@
 	re *regexp.Regexp
 }
 
-func (m *regexMatcher) Test(value string) bool {
+func (m *regexMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return m.re.MatchString(value)
 }
 
@@ -330,7 +334,7 @@
 	allowed []string
 }
 
-func (m *notInListMatcher) Test(value string) bool {
+func (m *notInListMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return !InList(value, m.allowed)
 }
 
@@ -340,7 +344,7 @@
 
 type isSetMatcher struct{}
 
-func (m *isSetMatcher) Test(value string) bool {
+func (m *isSetMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return value != ""
 }
 
@@ -350,6 +354,19 @@
 
 var isSetMatcherInstance = &isSetMatcher{}
 
+type sdkVersionMatcher struct {
+	condition   func(ctx ValueMatcherContext, spec SdkSpec) bool
+	description string
+}
+
+func (m *sdkVersionMatcher) Test(ctx ValueMatcherContext, value string) bool {
+	return m.condition(ctx, SdkSpecFromWithConfig(ctx.Config(), value))
+}
+
+func (m *sdkVersionMatcher) String() string {
+	return ".sdk-version(" + m.description + ")"
+}
+
 type ruleProperty struct {
 	fields  []string // e.x.: Vndk.Enabled
 	matcher ValueMatcher
@@ -563,9 +580,10 @@
 	return (len(r.moduleTypes) == 0 || InList(moduleType, r.moduleTypes)) && !InList(moduleType, r.unlessModuleTypes)
 }
 
-func (r *rule) appliesToProperties(properties []interface{}) bool {
-	includeProps := hasAllProperties(properties, r.props)
-	excludeProps := hasAnyProperty(properties, r.unlessProps)
+func (r *rule) appliesToProperties(ctx ValueMatcherContext,
+	properties []interface{}) bool {
+	includeProps := hasAllProperties(ctx, properties, r.props)
+	excludeProps := hasAnyProperty(ctx, properties, r.unlessProps)
 	return includeProps && !excludeProps
 }
 
@@ -585,6 +603,16 @@
 	return &notInListMatcher{allowed}
 }
 
+func LessThanSdkVersion(sdk string) ValueMatcher {
+	return &sdkVersionMatcher{
+		condition: func(ctx ValueMatcherContext, spec SdkSpec) bool {
+			return spec.ApiLevel.LessThan(
+				SdkSpecFromWithConfig(ctx.Config(), sdk).ApiLevel)
+		},
+		description: "lessThan=" + sdk,
+	}
+}
+
 // assorted utils
 
 func cleanPaths(paths []string) []string {
@@ -603,25 +631,28 @@
 	return names
 }
 
-func hasAnyProperty(properties []interface{}, props []ruleProperty) bool {
+func hasAnyProperty(ctx ValueMatcherContext, properties []interface{},
+	props []ruleProperty) bool {
 	for _, v := range props {
-		if hasProperty(properties, v) {
+		if hasProperty(ctx, properties, v) {
 			return true
 		}
 	}
 	return false
 }
 
-func hasAllProperties(properties []interface{}, props []ruleProperty) bool {
+func hasAllProperties(ctx ValueMatcherContext, properties []interface{},
+	props []ruleProperty) bool {
 	for _, v := range props {
-		if !hasProperty(properties, v) {
+		if !hasProperty(ctx, properties, v) {
 			return false
 		}
 	}
 	return true
 }
 
-func hasProperty(properties []interface{}, prop ruleProperty) bool {
+func hasProperty(ctx ValueMatcherContext, properties []interface{},
+	prop ruleProperty) bool {
 	for _, propertyStruct := range properties {
 		propertiesValue := reflect.ValueOf(propertyStruct).Elem()
 		for _, v := range prop.fields {
@@ -635,7 +666,7 @@
 		}
 
 		check := func(value string) bool {
-			return prop.matcher.Test(value)
+			return prop.matcher.Test(ctx, value)
 		}
 
 		if matchValue(propertiesValue, check) {
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index edda244..18a8705 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -296,6 +296,48 @@
 			"Only boot images and seapp contexts may be imported as a makefile goal.",
 		},
 	},
+	{
+		name: "min_sdk too low",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+				java_library {
+					name: "min_sdk_too_low",
+					min_sdk_version: "30",
+				}`),
+		},
+		rules: []Rule{
+			NeverAllow().WithMatcher("min_sdk_version", LessThanSdkVersion("31")),
+		},
+		expectedErrors: []string{
+			"module \"min_sdk_too_low\": violates neverallow",
+		},
+	},
+	{
+		name: "min_sdk high enough",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+				java_library {
+					name: "min_sdk_high_enough",
+					min_sdk_version: "31",
+				}`),
+		},
+		rules: []Rule{
+			NeverAllow().WithMatcher("min_sdk_version", LessThanSdkVersion("31")),
+		},
+	},
+	{
+		name: "current min_sdk high enough",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+				java_library {
+					name: "current_min_sdk_high_enough",
+					min_sdk_version: "current",
+				}`),
+		},
+		rules: []Rule{
+			NeverAllow().WithMatcher("min_sdk_version", LessThanSdkVersion("31")),
+		},
+	},
 }
 
 var prepareForNeverAllowTest = GroupFixturePreparers(
@@ -379,9 +421,10 @@
 }
 
 type mockJavaLibraryProperties struct {
-	Libs           []string
-	Sdk_version    *string
-	Uncompress_dex *bool
+	Libs            []string
+	Min_sdk_version *string
+	Sdk_version     *string
+	Uncompress_dex  *bool
 }
 
 type mockJavaLibraryModule struct {
diff --git a/apex/apex.go b/apex/apex.go
index 1e7ce7f..0a785f3 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -3200,15 +3200,16 @@
 			BootclasspathJar().
 			With("apex_available", module_name).
 			WithMatcher("permitted_packages", android.NotInList(module_packages)).
+			WithMatcher("min_sdk_version", android.LessThanSdkVersion("Tiramisu")).
 			Because("jars that are part of the " + module_name +
 				" module may only allow these packages: " + strings.Join(module_packages, ",") +
-				". Please jarjar or move code around.")
+				" with min_sdk < T. Please jarjar or move code around.")
 		rules = append(rules, permittedPackagesRule)
 	}
 	return rules
 }
 
-// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
+// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART on Q/R/S.
 // Adding code to the bootclasspath in new packages will cause issues on module update.
 func qModulesPackages() map[string][]string {
 	return map[string][]string{
@@ -3222,7 +3223,7 @@
 	}
 }
 
-// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
+// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART on R/S.
 // Adding code to the bootclasspath in new packages will cause issues on module update.
 func rModulesPackages() map[string][]string {
 	return map[string][]string{
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 3b9b13a..3cc312d 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -7359,6 +7359,7 @@
 					apex_available: ["myapex"],
 					sdk_version: "none",
 					system_modules: "none",
+					min_sdk_version: "30",
 				}
 				java_library {
 					name: "nonbcp_lib2",
@@ -7367,9 +7368,11 @@
 					permitted_packages: ["a.b"],
 					sdk_version: "none",
 					system_modules: "none",
+					min_sdk_version: "30",
 				}
 				apex {
 					name: "myapex",
+					min_sdk_version: "30",
 					key: "myapex.key",
 					java_libs: ["bcp_lib1", "nonbcp_lib2"],
 					updatable: false,
@@ -7382,8 +7385,8 @@
 			},
 		},
 		{
-			name:          "Bootclasspath apex jar not satisfying allowed module packages.",
-			expectedError: `module "bcp_lib2" .* which is restricted because jars that are part of the myapex module may only allow these packages: foo.bar. Please jarjar or move code around.`,
+			name:          "Bootclasspath apex jar not satisfying allowed module packages on Q.",
+			expectedError: `module "bcp_lib2" .* which is restricted because jars that are part of the myapex module may only allow these packages: foo.bar with min_sdk < T. Please jarjar or move code around.`,
 			bp: `
 				java_library {
 					name: "bcp_lib1",
@@ -7392,6 +7395,7 @@
 					permitted_packages: ["foo.bar"],
 					sdk_version: "none",
 					system_modules: "none",
+					min_sdk_version: "29",
 				}
 				java_library {
 					name: "bcp_lib2",
@@ -7400,9 +7404,85 @@
 					permitted_packages: ["foo.bar", "bar.baz"],
 					sdk_version: "none",
 					system_modules: "none",
+					min_sdk_version: "29",
 				}
 				apex {
 					name: "myapex",
+					min_sdk_version: "29",
+					key: "myapex.key",
+					java_libs: ["bcp_lib1", "bcp_lib2"],
+					updatable: false,
+				}
+			`,
+			bootJars: []string{"bcp_lib1", "bcp_lib2"},
+			modulesPackages: map[string][]string{
+				"myapex": []string{
+					"foo.bar",
+				},
+			},
+		},
+		{
+			name:          "Bootclasspath apex jar not satisfying allowed module packages on R.",
+			expectedError: `module "bcp_lib2" .* which is restricted because jars that are part of the myapex module may only allow these packages: foo.bar with min_sdk < T. Please jarjar or move code around.`,
+			bp: `
+				java_library {
+					name: "bcp_lib1",
+					srcs: ["lib1/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar"],
+					sdk_version: "none",
+					system_modules: "none",
+					min_sdk_version: "30",
+				}
+				java_library {
+					name: "bcp_lib2",
+					srcs: ["lib2/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar", "bar.baz"],
+					sdk_version: "none",
+					system_modules: "none",
+					min_sdk_version: "30",
+				}
+				apex {
+					name: "myapex",
+					min_sdk_version: "30",
+					key: "myapex.key",
+					java_libs: ["bcp_lib1", "bcp_lib2"],
+					updatable: false,
+				}
+			`,
+			bootJars: []string{"bcp_lib1", "bcp_lib2"},
+			modulesPackages: map[string][]string{
+				"myapex": []string{
+					"foo.bar",
+				},
+			},
+		},
+		{
+			name:          "Bootclasspath apex jar >= T not satisfying Q/R/S allowed module packages.",
+			expectedError: "",
+			bp: `
+				java_library {
+					name: "bcp_lib1",
+					srcs: ["lib1/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar"],
+					sdk_version: "none",
+					system_modules: "none",
+					min_sdk_version: "current",
+				}
+				java_library {
+					name: "bcp_lib2",
+					srcs: ["lib2/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar", "bar.baz"],
+					sdk_version: "none",
+					system_modules: "none",
+					min_sdk_version: "current",
+				}
+				apex {
+					name: "myapex",
+					min_sdk_version: "current",
 					key: "myapex.key",
 					java_libs: ["bcp_lib1", "bcp_lib2"],
 					updatable: false,