Merge "Fix incrementality issues for root_filesystem_config.txt" into main
diff --git a/android/module.go b/android/module.go
index 7786f13..713751a 100644
--- a/android/module.go
+++ b/android/module.go
@@ -2167,6 +2167,7 @@
 			} else {
 				hostRequired = m.baseProperties.Host_required
 			}
+			hostRequired = append(hostRequired, moduleInfoJSON.ExtraHostRequired...)
 
 			var data []string
 			for _, d := range ctx.testData {
diff --git a/android/module_info_json.go b/android/module_info_json.go
index bb309ff..50c961a 100644
--- a/android/module_info_json.go
+++ b/android/module_info_json.go
@@ -40,10 +40,12 @@
 	StaticDependencies  []string `json:"static_dependencies,omitempty"`   // $(sort $(ALL_MODULES.$(m).LOCAL_STATIC_LIBRARIES))
 	DataDependencies    []string `json:"data_dependencies,omitempty"`     // $(sort $(ALL_MODULES.$(m).TEST_DATA_BINS))
 
-	CompatibilitySuites []string `json:"compatibility_suites,omitempty"` // $(sort $(ALL_MODULES.$(m).COMPATIBILITY_SUITES))
-	AutoTestConfig      []string `json:"auto_test_config,omitempty"`     // $(ALL_MODULES.$(m).auto_test_config)
-	TestConfig          []string `json:"test_config,omitempty"`          // $(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS)
-	ExtraRequired       []string `json:"-"`
+	CompatibilitySuites  []string `json:"compatibility_suites,omitempty"` // $(sort $(ALL_MODULES.$(m).COMPATIBILITY_SUITES))
+	AutoTestConfig       []string `json:"auto_test_config,omitempty"`     // $(ALL_MODULES.$(m).auto_test_config)
+	TestConfig           []string `json:"test_config,omitempty"`          // $(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS)
+	TestModuleConfigBase string   `json:"test_module_config_base,omitempty"`
+	ExtraRequired        []string `json:"-"`
+	ExtraHostRequired    []string `json:"-"`
 
 	SupportedVariantsOverride []string `json:"-"`
 	Disabled                  bool     `json:"-"`
diff --git a/android/rule_builder.go b/android/rule_builder.go
index db56c3f..ea6aaa4 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -1187,7 +1187,11 @@
 // Textf adds the specified formatted text to the command line.  The text should not contain input or output paths or
 // the rule will not have them listed in its dependencies or outputs.
 func (c *RuleBuilderCommand) Textf(format string, a ...interface{}) *RuleBuilderCommand {
-	return c.Text(fmt.Sprintf(format, a...))
+	if c.buf.Len() > 0 {
+		c.buf.WriteByte(' ')
+	}
+	fmt.Fprintf(&c.buf, format, a...)
+	return c
 }
 
 // Flag adds the specified raw text to the command line.  The text should not contain input or output paths or the
diff --git a/apex/apex_test.go b/apex/apex_test.go
index a0a42a1..36a6974 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -11667,7 +11667,7 @@
 
 	// Arguments passed to aconfig to retrieve the state of the flags defined in the
 	// textproto files
-	aconfigFlagArgs := m.Output("released-flagged-apis-exportable.txt").Args["flags_path"]
+	aconfigFlagArgs := m.Output("released-flags-exportable.pb").Args["flags_path"]
 
 	// "bar-lib" is a static_lib of "foo" and is passed to metalava as classpath. Thus the
 	// cache file provided by the associated aconfig_declarations module "bar" should be passed
diff --git a/cc/afdo.go b/cc/afdo.go
index 8d8341e..1233e33 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -105,6 +105,9 @@
 		// The flags are prepended to allow overriding.
 		profileUseFlag := fmt.Sprintf(afdoFlagsFormat, fdoProfilePath)
 		flags.Local.CFlags = append([]string{profileUseFlag}, flags.Local.CFlags...)
+		// Salvage stale profile by fuzzy matching and use the remapped location for sample profile query.
+		flags.Local.CFlags = append([]string{"-mllvm", "--salvage-stale-profile=true"}, flags.Local.CFlags...)
+		flags.Local.CFlags = append([]string{"-mllvm", "--salvage-stale-profile-max-callsites=2000"}, flags.Local.CFlags...)
 		flags.Local.LdFlags = append([]string{profileUseFlag, "-Wl,-mllvm,-no-warn-sample-unused=true"}, flags.Local.LdFlags...)
 
 		// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
diff --git a/dexpreopt/Android.bp b/dexpreopt/Android.bp
index ea3f52b..dbc1922 100644
--- a/dexpreopt/Android.bp
+++ b/dexpreopt/Android.bp
@@ -9,6 +9,7 @@
         "class_loader_context.go",
         "config.go",
         "dexpreopt.go",
+        "dexpreopt_tools_zip.go",
         "system_server_zip.go",
         "testing.go",
     ],
diff --git a/dexpreopt/dexpreopt_tools_zip.go b/dexpreopt/dexpreopt_tools_zip.go
new file mode 100644
index 0000000..c7cf128
--- /dev/null
+++ b/dexpreopt/dexpreopt_tools_zip.go
@@ -0,0 +1,62 @@
+package dexpreopt
+
+import "android/soong/android"
+
+func init() {
+	android.InitRegistrationContext.RegisterSingletonType("dexpreopt_tools_zip_singleton", dexpreoptToolsZipSingletonFactory)
+}
+
+func dexpreoptToolsZipSingletonFactory() android.Singleton {
+	return &dexpreoptToolsZipSingleton{}
+}
+
+type dexpreoptToolsZipSingleton struct{}
+
+func (s *dexpreoptToolsZipSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	// The mac build doesn't build dex2oat, so create the zip file only if the build OS is linux.
+	if !ctx.Config().BuildOS.Linux() {
+		return
+	}
+	global := GetGlobalConfig(ctx)
+	if global.DisablePreopt {
+		return
+	}
+	config := GetCachedGlobalSoongConfig(ctx)
+	if config == nil {
+		return
+	}
+
+	deps := android.Paths{
+		ctx.Config().HostToolPath(ctx, "dexpreopt_gen"),
+		ctx.Config().HostToolPath(ctx, "dexdump"),
+		ctx.Config().HostToolPath(ctx, "oatdump"),
+		config.Profman,
+		config.Dex2oat,
+		config.Aapt,
+		config.SoongZip,
+		config.Zip2zip,
+		config.ManifestCheck,
+		config.ConstructContext,
+		config.UffdGcFlag,
+	}
+
+	out := android.PathForOutput(ctx, "dexpreopt_tools.zip")
+	builder := android.NewRuleBuilder(pctx, ctx)
+
+	cmd := builder.Command().BuiltTool("soong_zip").
+		Flag("-d").
+		FlagWithOutput("-o ", out).
+		Flag("-j")
+
+	for _, dep := range deps {
+		cmd.FlagWithInput("-f ", dep)
+	}
+
+	// This reads through a symlink to include the file it points to. This isn't great for
+	// build reproducibility, will need to be revisited later.
+	cmd.Textf("-f $(realpath %s)", config.Dex2oat)
+
+	builder.Build("dexpreopt_tools_zip", "building dexpreopt_tools.zip")
+
+	ctx.DistForGoal("droidcore", out)
+}
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index 2807446..3d306e3 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -337,6 +337,10 @@
 		ctx.DistForGoalWithFilename("droidcore-unbundled", a.proguardDictZip, namePrefix+insertBeforeExtension(a.proguardDictZip.Base(), "-FILE_NAME_TAG_PLACEHOLDER"))
 		ctx.DistForGoalWithFilename("droidcore-unbundled", a.proguardDictMapping, namePrefix+insertBeforeExtension(a.proguardDictMapping.Base(), "-FILE_NAME_TAG_PLACEHOLDER"))
 		ctx.DistForGoalWithFilename("droidcore-unbundled", a.proguardUsageZip, namePrefix+insertBeforeExtension(a.proguardUsageZip.Base(), "-FILE_NAME_TAG_PLACEHOLDER"))
+
+		if a.deviceProps.Android_info != nil {
+			ctx.DistForGoal("droidcore-unbundled", android.PathForModuleSrc(ctx, *a.deviceProps.Android_info))
+		}
 	}
 }
 
diff --git a/java/builder.go b/java/builder.go
index ade9784..dff0032 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -310,7 +310,7 @@
 
 	gatherReleasedFlaggedApisRule = pctx.AndroidStaticRule("gatherReleasedFlaggedApisRule",
 		blueprint.RuleParams{
-			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}' ` +
+			Command: `${aconfig} dump-cache --dedup --format=protobuf ` +
 				`--out ${out} ` +
 				`${flags_path} ` +
 				`${filter_args} `,
@@ -320,8 +320,8 @@
 
 	generateMetalavaRevertAnnotationsRule = pctx.AndroidStaticRule("generateMetalavaRevertAnnotationsRule",
 		blueprint.RuleParams{
-			Command:     `${keep-flagged-apis} ${in} > ${out}`,
-			CommandDeps: []string{"${keep-flagged-apis}"},
+			Command:     `${aconfig-to-metalava-flags} ${in} > ${out}`,
+			CommandDeps: []string{"${aconfig-to-metalava-flags}"},
 		})
 
 	generateApiXMLRule = pctx.AndroidStaticRule("generateApiXMLRule",
@@ -339,7 +339,7 @@
 	pctx.HostBinToolVariable("aconfig", "aconfig")
 	pctx.HostBinToolVariable("ravenizer", "ravenizer")
 	pctx.HostBinToolVariable("apimapper", "apimapper")
-	pctx.HostBinToolVariable("keep-flagged-apis", "keep-flagged-apis")
+	pctx.HostBinToolVariable("aconfig-to-metalava-flags", "aconfig-to-metalava-flags")
 }
 
 type javaBuilderFlags struct {
diff --git a/java/droidstubs.go b/java/droidstubs.go
index caad688..b3241cc 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -821,19 +821,17 @@
 		}
 	}
 
-	if len(aconfigFlagsPaths) == 0 {
-		// This argument should not be added for "everything" stubs
-		cmd.Flag("--revert-annotation android.annotation.FlaggedApi")
-		return
-	}
+	// If aconfigFlagsPaths is empty then it is still important to generate the
+	// Metalava flags config file, albeit with an empty set of flags, so that all
+	// flagged APIs will be reverted.
 
-	releasedFlaggedApisFile := android.PathForModuleOut(ctx, fmt.Sprintf("released-flagged-apis-%s.txt", stubsType.String()))
-	revertAnnotationsFile := android.PathForModuleOut(ctx, fmt.Sprintf("revert-annotations-%s.txt", stubsType.String()))
+	releasedFlagsFile := android.PathForModuleOut(ctx, fmt.Sprintf("released-flags-%s.pb", stubsType.String()))
+	metalavaFlagsConfigFile := android.PathForModuleOut(ctx, fmt.Sprintf("flags-config-%s.xml", stubsType.String()))
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        gatherReleasedFlaggedApisRule,
 		Inputs:      aconfigFlagsPaths,
-		Output:      releasedFlaggedApisFile,
+		Output:      releasedFlagsFile,
 		Description: fmt.Sprintf("%s gather aconfig flags", stubsType),
 		Args: map[string]string{
 			"flags_path":  android.JoinPathsWithPrefix(aconfigFlagsPaths, "--cache "),
@@ -843,12 +841,12 @@
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        generateMetalavaRevertAnnotationsRule,
-		Input:       releasedFlaggedApisFile,
-		Output:      revertAnnotationsFile,
-		Description: fmt.Sprintf("%s revert annotations", stubsType),
+		Input:       releasedFlagsFile,
+		Output:      metalavaFlagsConfigFile,
+		Description: fmt.Sprintf("%s metalava flags config", stubsType),
 	})
 
-	cmd.FlagWithInput("@", revertAnnotationsFile)
+	cmd.FlagWithInput("--config-file ", metalavaFlagsConfigFile)
 }
 
 func (d *Droidstubs) commonMetalavaStubCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index dfdf877..1f9d223 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -410,12 +410,12 @@
 
 	m := result.ModuleForTests(t, "foo", "android_common")
 	android.AssertStringDoesContain(t, "foo generates revert annotations file",
-		strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
+		strings.Join(m.AllOutputs(), ""), "flags-config-exportable.xml")
 
 	// revert-annotations.txt passed to exportable stubs generation metalava command
 	manifest := m.Output("metalava_exportable.sbox.textproto")
 	cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
-	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
+	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "flags-config-exportable.xml")
 
 	android.AssertStringDoesContain(t, "foo generates exportable stubs jar",
 		strings.Join(m.AllOutputs(), ""), "exportable/foo-stubs.srcjar")
@@ -460,7 +460,7 @@
 
 	m := result.ModuleForTests(t, "foo", "android_common")
 
-	rule := m.Output("released-flagged-apis-exportable.txt")
+	rule := m.Output("released-flags-exportable.pb")
 	exposeWritableApisFilter := "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'"
 	android.AssertStringEquals(t, "Filter argument expected to contain READ_WRITE permissions", exposeWritableApisFilter, rule.Args["filter_args"])
 }
diff --git a/java/java_test.go b/java/java_test.go
index a6290a6..6df81e5 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -2912,12 +2912,12 @@
 
 	m := result.ModuleForTests(t, "foo", "android_common")
 	android.AssertStringDoesContain(t, "foo generates revert annotations file",
-		strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
+		strings.Join(m.AllOutputs(), ""), "flags-config-exportable.xml")
 
 	// revert-annotations.txt passed to exportable stubs generation metalava command
 	manifest := m.Output("metalava.sbox.textproto")
 	cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
-	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
+	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "flags-config-exportable.xml")
 }
 
 func TestTestOnly(t *testing.T) {
diff --git a/java/sdk.go b/java/sdk.go
index ab1c653..73262da 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -382,7 +382,7 @@
 
 	rule.Build("api_fingerprint", "generate api_fingerprint.txt")
 
-	if ctx.Config().BuildOS == android.Linux {
+	if ctx.Config().BuildOS.Linux() {
 		ctx.DistForGoals([]string{"sdk", "droidcore"}, out)
 	}
 }
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 94163a5..c0e13d5 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -287,9 +287,13 @@
     ],
 }
 
-sh_binary_host {
-    name: "keep-flagged-apis",
-    src: "keep-flagged-apis.sh",
+python_binary_host {
+    name: "aconfig-to-metalava-flags",
+    main: "aconfig-to-metalava-flags.py",
+    srcs: ["aconfig-to-metalava-flags.py"],
+    libs: [
+        "libaconfig_python_proto",
+    ],
 }
 
 python_binary_host {
diff --git a/scripts/aconfig-to-metalava-flags.py b/scripts/aconfig-to-metalava-flags.py
new file mode 100644
index 0000000..efa85ec
--- /dev/null
+++ b/scripts/aconfig-to-metalava-flags.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2025 The Android Open Source Project
+#
+# 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.
+
+"""Converts a set of aconfig protobuf flags to a Metalava config file."""
+
+# Formatted using `pyformat -i scripts/aconfig-to-metalava-flags.py`
+
+import argparse
+import sys
+import xml.etree.ElementTree as ET
+
+from protos import aconfig_pb2
+
+_READ_ONLY = aconfig_pb2.flag_permission.READ_ONLY
+_ENABLED = aconfig_pb2.flag_state.ENABLED
+_DISABLED = aconfig_pb2.flag_state.DISABLED
+
+# The namespace of the Metalava config file.
+CONFIG_NS = 'http://www.google.com/tools/metalava/config'
+
+
+def config_name(tag: str):
+  """Create a QName in the config namespace.
+
+  :param:tag the name of the entity in the config namespace.
+  """
+  return f'{{{CONFIG_NS}}}{tag}'
+
+
+def main():
+  """Program entry point."""
+  args_parser = argparse.ArgumentParser(
+      description='Generate Metalava flags config from aconfig protobuf',
+  )
+  args_parser.add_argument(
+      'input',
+      help='The path to the aconfig protobuf file',
+  )
+  args = args_parser.parse_args(sys.argv[1:])
+
+  # Read the parsed_flags from the protobuf file.
+  with open(args.input, 'rb') as f:
+    parsed_flags = aconfig_pb2.parsed_flags.FromString(f.read())
+
+  # Create the structure of the XML config file.
+  config = ET.Element(config_name('config'))
+  api_flags = ET.SubElement(config, config_name('api-flags'))
+  # Create an <api-flag> element for each parsed_flag.
+  for flag in parsed_flags.parsed_flag:
+    if flag.permission == _READ_ONLY:
+      # Ignore any read only disabled flags as Metalava assumes that as the
+      # default when an <api-flags/> element is provided so this reduces the
+      # size of the file.
+      if flag.state == _DISABLED:
+        continue
+      mutability = 'immutable'
+    else:
+      mutability = 'mutable'
+    if flag.state == _ENABLED:
+      status = 'enabled'
+    else:
+      status = 'disabled'
+    attributes = {
+        'package': flag.package,
+        'name': flag.name,
+        'mutability': mutability,
+        'status': status,
+    }
+    # Convert the attribute names into qualified names in, what will become, the
+    # default namespace for the XML file. This is needed to ensure that the
+    # attribute will be written in the XML file without a prefix, e.g.
+    # `name="flag_name"`. Without it, a namespace prefix, e.g. `ns1`, will be
+    # synthesized for the attribute when writing the XML file, i.e. it
+    # will be written as `ns1:name="flag_name"`. Strictly speaking, that is
+    # unnecessary as the "Namespaces in XML 1.0 (Third Edition)" specification
+    # says that unprefixed attribute names have no namespace.
+    qualified_attributes = {config_name(k): v for (k, v) in attributes.items()}
+    ET.SubElement(api_flags, config_name('api-flag'), qualified_attributes)
+
+  # Create a tree and add white space so it will pretty print when written out.
+  tree = ET.ElementTree(config)
+  ET.indent(tree)
+
+  # Write the tree using the config namespace as the default.
+  tree.write(sys.stdout, encoding='unicode', default_namespace=CONFIG_NS)
+  sys.stdout.close()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/scripts/keep-flagged-apis.sh b/scripts/keep-flagged-apis.sh
deleted file mode 100755
index 48efb7a..0000000
--- a/scripts/keep-flagged-apis.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/bash -e
-#
-# Copyright 2023 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.
-
-# Convert a list of flags in the input file to a list of metalava options
-# that will keep the APIs for those flags will hiding all other flagged
-# APIs.
-
-FLAGS="$1"
-
-FLAGGED="android.annotation.FlaggedApi"
-
-# Convert the list of feature flags in the input file to Metalava options
-# of the form `--revert-annotation !android.annotation.FlaggedApi("<flag>")`
-# to prevent the annotated APIs from being hidden, i.e. include the annotated
-# APIs in the SDK snapshots.
-while read -r line; do
-  # Escape and quote the key for sed
-  escaped_line=$(echo "$line" | sed "s/'/\\\'/g; s/ /\\ /g")
-
-  echo "--revert-annotation '!$FLAGGED(\"$escaped_line\")'"
-done < "$FLAGS"
-
-# Revert all flagged APIs, unless listed above.
-echo "--revert-annotation $FLAGGED"
diff --git a/tradefed_modules/test_module_config.go b/tradefed_modules/test_module_config.go
index 988352c..6dd48eb 100644
--- a/tradefed_modules/test_module_config.go
+++ b/tradefed_modules/test_module_config.go
@@ -162,6 +162,22 @@
 	m.validateBase(ctx, &testModuleConfigTag, "android_test", false)
 	m.generateManifestAndConfig(ctx)
 
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Class = []string{m.provider.MkAppClass}
+	if m.provider.MkAppClass != "NATIVE_TESTS" {
+		moduleInfoJSON.Tags = append(moduleInfoJSON.Tags, "tests")
+	}
+	if m.provider.IsUnitTest {
+		moduleInfoJSON.IsUnitTest = "true"
+	}
+	moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, m.tradefedProperties.Test_suites...)
+	moduleInfoJSON.SystemSharedLibs = []string{"none"}
+	moduleInfoJSON.ExtraRequired = append(moduleInfoJSON.ExtraRequired, m.provider.RequiredModuleNames...)
+	moduleInfoJSON.ExtraRequired = append(moduleInfoJSON.ExtraRequired, *m.Base)
+	moduleInfoJSON.ExtraHostRequired = append(moduleInfoJSON.ExtraRequired, m.provider.HostRequiredModuleNames...)
+	moduleInfoJSON.TestConfig = []string{m.testConfig.String()}
+	moduleInfoJSON.AutoTestConfig = []string{"true"}
+	moduleInfoJSON.TestModuleConfigBase = proptools.String(m.Base)
 }
 
 // Ensure at least one test_suite is listed.  Ideally it should be general-tests