Merge "Export the license metadata files for dexpreopted bootjars to Make"
diff --git a/android/paths.go b/android/paths.go
index 05caebd..e7829b9 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1474,14 +1474,11 @@
 func PathForVndkRefAbiDump(ctx ModuleInstallPathContext, version, fileName string,
 	isNdk, isLlndkOrVndk, isGzip bool) OptionalPath {
 
-	arches := ctx.DeviceConfig().Arches()
-	if len(arches) == 0 {
-		panic("device build with no primary arch")
-	}
-	currentArch := ctx.Arch()
-	archNameAndVariant := currentArch.ArchType.String()
-	if currentArch.ArchVariant != "" {
-		archNameAndVariant += "_" + currentArch.ArchVariant
+	currentArchType := ctx.Arch().ArchType
+	primaryArchType := ctx.Config().DevicePrimaryArchType()
+	archName := currentArchType.String()
+	if currentArchType != primaryArchType {
+		archName += "_" + primaryArchType.String()
 	}
 
 	var dirName string
@@ -1503,7 +1500,7 @@
 	}
 
 	return ExistentPathForSource(ctx, "prebuilts", "abi-dumps", dirName,
-		version, binderBitness, archNameAndVariant, "source-based",
+		version, binderBitness, archName, "source-based",
 		fileName+ext)
 }
 
diff --git a/java/legacy_core_platform_api_usage.go b/java/legacy_core_platform_api_usage.go
index e3396c1..8e22491 100644
--- a/java/legacy_core_platform_api_usage.go
+++ b/java/legacy_core_platform_api_usage.go
@@ -81,7 +81,6 @@
 	"ds-car-docs", // for AAOS API documentation only
 	"DynamicSystemInstallationService",
 	"EmergencyInfo-lib",
-	"ethernet-service",
 	"EthernetServiceTests",
 	"ExternalStorageProvider",
 	"face-V1-0-javalib",
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index d8b88b2..35d7d4d 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -407,6 +407,8 @@
 	dependentModules map[string]*moduleInfo
 	soongNamespaces  map[string]map[string]bool
 	includeTops      []string
+	typeHints        map[string]starlarkType
+	atTopOfMakefile  bool
 }
 
 func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
@@ -450,6 +452,8 @@
 		dependentModules: make(map[string]*moduleInfo),
 		soongNamespaces:  make(map[string]map[string]bool),
 		includeTops:      []string{},
+		typeHints:        make(map[string]starlarkType),
+		atTopOfMakefile:  true,
 	}
 	ctx.pushVarAssignments()
 	for _, item := range predefined {
@@ -1680,7 +1684,8 @@
 	// Clear the includeTops after each non-comment statement
 	// so that include annotations placed on certain statements don't apply
 	// globally for the rest of the makefile was well.
-	if _, wasComment := node.(*mkparser.Comment); !wasComment && len(ctx.includeTops) > 0 {
+	if _, wasComment := node.(*mkparser.Comment); !wasComment {
+		ctx.atTopOfMakefile = false
 		ctx.includeTops = []string{}
 	}
 
@@ -1690,6 +1695,12 @@
 	return result
 }
 
+// The types allowed in a type_hint
+var typeHintMap = map[string]starlarkType{
+	"string": starlarkTypeString,
+	"list":   starlarkTypeList,
+}
+
 // Processes annotation. An annotation is a comment that starts with #RBC# and provides
 // a conversion hint -- say, where to look for the dynamically calculated inherit/include
 // paths. Returns true if the comment was a successfully-handled annotation.
@@ -1714,6 +1725,35 @@
 		}
 		ctx.includeTops = append(ctx.includeTops, p)
 		return nil, true
+	} else if p, ok := maybeTrim(annotation, "type_hint"); ok {
+		// Type hints must come at the beginning the file, to avoid confusion
+		// if a type hint was specified later and thus only takes effect for half
+		// of the file.
+		if !ctx.atTopOfMakefile {
+			return ctx.newBadNode(cnode, "type_hint annotations must come before the first Makefile statement"), true
+		}
+
+		parts := strings.Fields(p)
+		if len(parts) <= 1 {
+			return ctx.newBadNode(cnode, "Invalid type_hint annotation: %s. Must be a variable type followed by a list of variables of that type", p), true
+		}
+
+		var varType starlarkType
+		if varType, ok = typeHintMap[parts[0]]; !ok {
+			varType = starlarkTypeUnknown
+		}
+		if varType == starlarkTypeUnknown {
+			return ctx.newBadNode(cnode, "Invalid type_hint annotation. Only list/string types are accepted, found %s", parts[0]), true
+		}
+
+		for _, name := range parts[1:] {
+			// Don't allow duplicate type hints
+			if _, ok := ctx.typeHints[name]; ok {
+				return ctx.newBadNode(cnode, "Duplicate type hint for variable %s", name), true
+			}
+			ctx.typeHints[name] = varType
+		}
+		return nil, true
 	}
 	return ctx.newBadNode(cnode, "unsupported annotation %s", cnode.Comment), true
 }
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 35c54d2..4084660 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -1406,6 +1406,53 @@
     pass
 `,
 	},
+	{
+		desc:   "Type hints",
+		mkname: "product.mk",
+		in: `
+# Test type hints
+#RBC# type_hint list MY_VAR MY_VAR_2
+# Unsupported type
+#RBC# type_hint bool MY_VAR_3
+# Invalid syntax
+#RBC# type_hint list
+# Duplicated variable
+#RBC# type_hint list MY_VAR_2
+#RBC# type_hint list my-local-var-with-dashes
+
+MY_VAR := foo
+MY_VAR_UNHINTED := foo
+
+# Vars set after other statements still get the hint
+MY_VAR_2 := foo
+
+# You can't specify a type hint after the first statement
+#RBC# type_hint list MY_VAR_4
+MY_VAR_4 := foo
+
+my-local-var-with-dashes := foo
+`,
+		expected: `# Test type hints
+# Unsupported type
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.mk2rbc_error("product.mk:5", "Invalid type_hint annotation. Only list/string types are accepted, found bool")
+  # Invalid syntax
+  rblf.mk2rbc_error("product.mk:7", "Invalid type_hint annotation: list. Must be a variable type followed by a list of variables of that type")
+  # Duplicated variable
+  rblf.mk2rbc_error("product.mk:9", "Duplicate type hint for variable MY_VAR_2")
+  g["MY_VAR"] = ["foo"]
+  g["MY_VAR_UNHINTED"] = "foo"
+  # Vars set after other statements still get the hint
+  g["MY_VAR_2"] = ["foo"]
+  # You can't specify a type hint after the first statement
+  rblf.mk2rbc_error("product.mk:19", "type_hint annotations must come before the first Makefile statement")
+  g["MY_VAR_4"] = "foo"
+  _my_local_var_with_dashes = ["foo"]
+`,
+	},
 }
 
 var known_variables = []struct {
diff --git a/mk2rbc/variable.go b/mk2rbc/variable.go
index 3241a38..506266a 100644
--- a/mk2rbc/variable.go
+++ b/mk2rbc/variable.go
@@ -291,6 +291,12 @@
 // addVariable returns a variable with a given name. A variable is
 // added if it does not exist yet.
 func (ctx *parseContext) addVariable(name string) variable {
+	// Get the hintType before potentially changing the variable name
+	var hintType starlarkType
+	var ok bool
+	if hintType, ok = ctx.typeHints[name]; !ok {
+		hintType = starlarkTypeUnknown
+	}
 	// Heuristics: if variable's name is all lowercase, consider it local
 	// string variable.
 	isLocalVariable := name == strings.ToLower(name)
@@ -301,8 +307,8 @@
 	}
 	v, found := ctx.variables[name]
 	if !found {
-		_, preset := presetVariables[name]
 		if vi, found := KnownVariables[name]; found {
+			_, preset := presetVariables[name]
 			switch vi.class {
 			case VarClassConfig:
 				v = &productConfigVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}}
@@ -310,10 +316,10 @@
 				v = &otherGlobalVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}}
 			}
 		} else if isLocalVariable {
-			v = &localVariable{baseVariable{nam: name, typ: starlarkTypeUnknown}}
+			v = &localVariable{baseVariable{nam: name, typ: hintType}}
 		} else {
-			vt := starlarkTypeUnknown
-			if strings.HasPrefix(name, "LOCAL_") {
+			vt := hintType
+			if strings.HasPrefix(name, "LOCAL_") && vt == starlarkTypeUnknown {
 				// Heuristics: local variables that contribute to corresponding config variables
 				if cfgVarName, found := localProductConfigVariables[name]; found {
 					vi, found2 := KnownVariables[cfgVarName]
diff --git a/scripts/hiddenapi/signature_trie.py b/scripts/hiddenapi/signature_trie.py
index 2ea8c49..e813a97 100644
--- a/scripts/hiddenapi/signature_trie.py
+++ b/scripts/hiddenapi/signature_trie.py
@@ -22,6 +22,19 @@
 
 @dataclasses.dataclass()
 class Node:
+    """A node in the signature trie."""
+
+    # The type of the node.
+    #
+    # Leaf nodes are of type "member".
+    # Interior nodes can be either "package", or "class".
+    type: str
+
+    # The selector of the node.
+    #
+    # That is a string that can be used to select the node, e.g. in a pattern
+    # that is passed to InteriorNode.get_matching_rows().
+    selector: str
 
     def values(self, selector):
         """Get the values from a set of selected nodes.
@@ -48,6 +61,10 @@
         """
         raise NotImplementedError("Please Implement this method")
 
+    def child_nodes(self):
+        """Get an iterable of the child nodes of this node."""
+        raise NotImplementedError("Please Implement this method")
+
 
 # pylint: disable=line-too-long
 @dataclasses.dataclass()
@@ -110,74 +127,149 @@
         #  0 - java/lang/Character$UnicodeScript
         #  1 - of(I)Ljava/lang/Character$UnicodeScript;
         parts = text.split(";->")
+        # If there is no member then this will be an empty list.
         member = parts[1:]
         # Split the qualified class name into packages, and class name.
         #  0 - java
         #  1 - lang
         #  2 - Character$UnicodeScript
         elements = parts[0].split("/")
-        packages = elements[0:-1]
-        class_name = elements[-1]
-        if class_name in ("*", "**"):  # pylint: disable=no-else-return
+        last_element = elements[-1]
+        wildcard = []
+        classes = []
+        if "*" in last_element:
+            if last_element not in ("*", "**"):
+                raise Exception(f"Invalid signature '{signature}': invalid "
+                                f"wildcard '{last_element}'")
+            packages = elements[0:-1]
             # Cannot specify a wildcard and target a specific member
-            if len(member) != 0:
-                raise Exception(f"Invalid signature {signature}: contains "
-                                f"wildcard {class_name} and "
-                                f"member signature {member[0]}")
-            wildcard = [class_name]
-            # Assemble the parts into a single list, adding prefixes to identify
-            # the different parts.
-            #  0 - package:java
-            #  1 - package:lang
-            #  2 - *
-            return list(chain(["package:" + x for x in packages], wildcard))
+            if member:
+                raise Exception(f"Invalid signature '{signature}': contains "
+                                f"wildcard '{last_element}' and "
+                                f"member signature '{member[0]}'")
+            wildcard = [last_element]
+        elif last_element.islower():
+            raise Exception(f"Invalid signature '{signature}': last element "
+                            f"'{last_element}' is lower case but should be an "
+                            f"upper case class name or wildcard")
         else:
+            packages = elements[0:-1]
             # Split the class name into outer / inner classes
             #  0 - Character
             #  1 - UnicodeScript
-            classes = class_name.split("$")
-            # Assemble the parts into a single list, adding prefixes to identify
-            # the different parts.
-            #  0 - package:java
-            #  1 - package:lang
-            #  2 - class:Character
-            #  3 - class:UnicodeScript
-            #  4 - member:of(I)Ljava/lang/Character$UnicodeScript;
-            return list(
-                chain(["package:" + x for x in packages],
-                      ["class:" + x for x in classes],
-                      ["member:" + x for x in member]))
+            classes = last_element.removesuffix(";").split("$")
+
+        # Assemble the parts into a single list, adding prefixes to identify
+        # the different parts. If a wildcard is provided then it looks something
+        # like this:
+        #  0 - package:java
+        #  1 - package:lang
+        #  2 - *
+        #
+        # Otherwise, it looks something like this:
+        #  0 - package:java
+        #  1 - package:lang
+        #  2 - class:Character
+        #  3 - class:UnicodeScript
+        #  4 - member:of(I)Ljava/lang/Character$UnicodeScript;
+        return list(
+            chain([("package", x) for x in packages],
+                  [("class", x) for x in classes],
+                  [("member", x) for x in member],
+                  [("wildcard", x) for x in wildcard]))
 
     # pylint: enable=line-too-long
 
-    def add(self, signature, value):
+    @staticmethod
+    def split_element(element):
+        element_type, element_value = element
+        return element_type, element_value
+
+    @staticmethod
+    def element_type(element):
+        element_type, _ = InteriorNode.split_element(element)
+        return element_type
+
+    @staticmethod
+    def elements_to_selector(elements):
+        """Compute a selector for a set of elements.
+
+        A selector uniquely identifies a specific Node in the trie. It is
+        essentially a prefix of a signature (without the leading L).
+
+        e.g. a trie containing "Ljava/lang/Object;->String()Ljava/lang/String;"
+        would contain nodes with the following selectors:
+        * "java"
+        * "java/lang"
+        * "java/lang/Object"
+        * "java/lang/Object;->String()Ljava/lang/String;"
+        """
+        signature = ""
+        preceding_type = ""
+        for element in elements:
+            element_type, element_value = InteriorNode.split_element(element)
+            separator = ""
+            if element_type == "package":
+                separator = "/"
+            elif element_type == "class":
+                if preceding_type == "class":
+                    separator = "$"
+                else:
+                    separator = "/"
+            elif element_type == "wildcard":
+                separator = "/"
+            elif element_type == "member":
+                separator += ";->"
+
+            if signature:
+                signature += separator
+
+            signature += element_value
+
+            preceding_type = element_type
+
+        return signature
+
+    def add(self, signature, value, only_if_matches=False):
         """Associate the value with the specific signature.
 
         :param signature: the member signature
         :param value: the value to associated with the signature
+        :param only_if_matches: True if the value is added only if the signature
+             matches at least one of the existing top level packages.
         :return: n/a
         """
         # Split the signature into elements.
         elements = self.signature_to_elements(signature)
         # Find the Node associated with the deepest class.
         node = self
-        for element in elements[:-1]:
+        for index, element in enumerate(elements[:-1]):
             if element in node.nodes:
                 node = node.nodes[element]
+            elif only_if_matches and index == 0:
+                return
             else:
-                next_node = InteriorNode()
+                selector = self.elements_to_selector(elements[0:index + 1])
+                next_node = InteriorNode(
+                    type=InteriorNode.element_type(element), selector=selector)
                 node.nodes[element] = next_node
                 node = next_node
         # Add a Leaf containing the value and associate it with the member
         # signature within the class.
         last_element = elements[-1]
-        if not last_element.startswith("member:"):
+        last_element_type = self.element_type(last_element)
+        if last_element_type != "member":
             raise Exception(
                 f"Invalid signature: {signature}, does not identify a "
                 "specific member")
         if last_element in node.nodes:
             raise Exception(f"Duplicate signature: {signature}")
-        node.nodes[last_element] = Leaf(value)
+        leaf = Leaf(
+            type=last_element_type,
+            selector=signature,
+            value=value,
+        )
+        node.nodes[last_element] = leaf
 
     def get_matching_rows(self, pattern):
         """Get the values (plural) associated with the pattern.
@@ -188,10 +280,6 @@
         If the pattern is a class then this will return a list containing the
         values associated with all members of that class.
 
-        If the pattern is a package then this will return a list containing the
-        values associated with all the members of all the classes in that
-        package and sub-packages.
-
         If the pattern ends with "*" then the preceding part is treated as a
         package and this will return a list containing the values associated
         with all the members of all the classes in that package.
@@ -213,11 +301,12 @@
         selector = lambda x: True
 
         last_element = elements[-1]
-        if last_element in ("*", "**"):
+        last_element_type, last_element_value = self.split_element(last_element)
+        if last_element_type == "wildcard":
             elements = elements[:-1]
-            if last_element == "*":
+            if last_element_value == "*":
                 # Do not include values from sub-packages.
-                selector = lambda x: not x.startswith("package:")
+                selector = lambda x: InteriorNode.element_type(x) != "package"
 
         for element in elements:
             if element in node.nodes:
@@ -236,6 +325,8 @@
             if selector(key):
                 node.append_values(values, lambda x: True)
 
+    def child_nodes(self):
+        return self.nodes.values()
 
 
 @dataclasses.dataclass()
@@ -251,6 +342,9 @@
     def append_values(self, values, selector):
         values.append([self.value])
 
+    def child_nodes(self):
+        return []
+
 
 def signature_trie():
-    return InteriorNode()
+    return InteriorNode(type="root", selector="")
diff --git a/scripts/hiddenapi/signature_trie_test.py b/scripts/hiddenapi/signature_trie_test.py
index 2dc79d0..1295691 100755
--- a/scripts/hiddenapi/signature_trie_test.py
+++ b/scripts/hiddenapi/signature_trie_test.py
@@ -27,99 +27,127 @@
     def signature_to_elements(signature):
         return InteriorNode.signature_to_elements(signature)
 
+    @staticmethod
+    def elements_to_signature(elements):
+        return InteriorNode.elements_to_selector(elements)
+
     def test_nested_inner_classes(self):
         elements = [
-            "package:java",
-            "package:lang",
-            "class:ProcessBuilder",
-            "class:Redirect",
-            "class:1",
-            "member:<init>()V",
+            ("package", "java"),
+            ("package", "lang"),
+            ("class", "ProcessBuilder"),
+            ("class", "Redirect"),
+            ("class", "1"),
+            ("member", "<init>()V"),
         ]
         signature = "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V"
         self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
 
     def test_basic_member(self):
         elements = [
-            "package:java",
-            "package:lang",
-            "class:Object",
-            "member:hashCode()I",
+            ("package", "java"),
+            ("package", "lang"),
+            ("class", "Object"),
+            ("member", "hashCode()I"),
         ]
         signature = "Ljava/lang/Object;->hashCode()I"
         self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
 
     def test_double_dollar_class(self):
         elements = [
-            "package:java",
-            "package:lang",
-            "class:CharSequence",
-            "class:",
-            "class:ExternalSyntheticLambda0",
-            "member:<init>(Ljava/lang/CharSequence;)V",
+            ("package", "java"),
+            ("package", "lang"),
+            ("class", "CharSequence"),
+            ("class", ""),
+            ("class", "ExternalSyntheticLambda0"),
+            ("member", "<init>(Ljava/lang/CharSequence;)V"),
         ]
         signature = "Ljava/lang/CharSequence$$ExternalSyntheticLambda0;" \
                     "-><init>(Ljava/lang/CharSequence;)V"
         self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
 
     def test_no_member(self):
         elements = [
-            "package:java",
-            "package:lang",
-            "class:CharSequence",
-            "class:",
-            "class:ExternalSyntheticLambda0",
+            ("package", "java"),
+            ("package", "lang"),
+            ("class", "CharSequence"),
+            ("class", ""),
+            ("class", "ExternalSyntheticLambda0"),
         ]
         signature = "Ljava/lang/CharSequence$$ExternalSyntheticLambda0"
         self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
 
     def test_wildcard(self):
         elements = [
-            "package:java",
-            "package:lang",
-            "*",
+            ("package", "java"),
+            ("package", "lang"),
+            ("wildcard", "*"),
         ]
         signature = "java/lang/*"
         self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, self.elements_to_signature(elements))
 
     def test_recursive_wildcard(self):
         elements = [
-            "package:java",
-            "package:lang",
-            "**",
+            ("package", "java"),
+            ("package", "lang"),
+            ("wildcard", "**"),
         ]
         signature = "java/lang/**"
         self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, self.elements_to_signature(elements))
 
     def test_no_packages_wildcard(self):
         elements = [
-            "*",
+            ("wildcard", "*"),
         ]
         signature = "*"
         self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, self.elements_to_signature(elements))
 
     def test_no_packages_recursive_wildcard(self):
         elements = [
-            "**",
+            ("wildcard", "**"),
         ]
         signature = "**"
         self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, self.elements_to_signature(elements))
+
+    def test_invalid_no_class_or_wildcard(self):
+        signature = "java/lang"
+        with self.assertRaises(Exception) as context:
+            self.signature_to_elements(signature)
+        self.assertIn(
+            "last element 'lang' is lower case but should be an "
+            "upper case class name or wildcard", str(context.exception))
 
     def test_non_standard_class_name(self):
         elements = [
-            "package:javax",
-            "package:crypto",
-            "class:extObjectInputStream",
+            ("package", "javax"),
+            ("package", "crypto"),
+            ("class", "extObjectInputStream"),
         ]
         signature = "Ljavax/crypto/extObjectInputStream"
         self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
+
+    def test_invalid_pattern_wildcard(self):
+        pattern = "Ljava/lang/Class*"
+        with self.assertRaises(Exception) as context:
+            self.signature_to_elements(pattern)
+        self.assertIn("invalid wildcard 'Class*'", str(context.exception))
 
     def test_invalid_pattern_wildcard_and_member(self):
         pattern = "Ljava/lang/*;->hashCode()I"
         with self.assertRaises(Exception) as context:
             self.signature_to_elements(pattern)
-        self.assertIn("contains wildcard * and member signature hashCode()I",
-                      str(context.exception))
+        self.assertIn(
+            "contains wildcard '*' and member signature 'hashCode()I'",
+            str(context.exception))
 
 
 class TestGetMatchingRows(unittest.TestCase):
@@ -185,6 +213,18 @@
             "Ljava/util/zip/ZipFile;-><clinit>()V",
         ])
 
+    def test_node_wildcard(self):
+        trie = self.read_trie()
+        node = list(trie.child_nodes())[0]
+        self.check_node_patterns(node, "**", [
+            "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;",
+            "Ljava/lang/Character;->serialVersionUID:J",
+            "Ljava/lang/Object;->hashCode()I",
+            "Ljava/lang/Object;->toString()Ljava/lang/String;",
+            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
+            "Ljava/util/zip/ZipFile;-><clinit>()V",
+        ])
+
     # pylint: enable=line-too-long