Merge "rust modules can be included in apex"
diff --git a/android/androidmk.go b/android/androidmk.go
index 4adbb22..063830b 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -177,24 +177,84 @@
 	a.EntryMap[name] = append(a.EntryMap[name], value...)
 }
 
-// Compute the list of Make strings to declare phone goals and dist-for-goals
-// calls from the module's dist and dists properties.
-func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
+// The contributions to the dist.
+type distContributions struct {
+	// List of goals and the dist copy instructions.
+	copiesForGoals []*copiesForGoals
+}
+
+// getCopiesForGoals returns a copiesForGoals into which copy instructions that
+// must be processed when building one or more of those goals can be added.
+func (d *distContributions) getCopiesForGoals(goals string) *copiesForGoals {
+	copiesForGoals := &copiesForGoals{goals: goals}
+	d.copiesForGoals = append(d.copiesForGoals, copiesForGoals)
+	return copiesForGoals
+}
+
+// Associates a list of dist copy instructions with a set of goals for which they
+// should be run.
+type copiesForGoals struct {
+	// goals are a space separated list of build targets that will trigger the
+	// copy instructions.
+	goals string
+
+	// A list of instructions to copy a module's output files to somewhere in the
+	// dist directory.
+	copies []distCopy
+}
+
+// Adds a copy instruction.
+func (d *copiesForGoals) addCopyInstruction(from Path, dest string) {
+	d.copies = append(d.copies, distCopy{from, dest})
+}
+
+// Instruction on a path that must be copied into the dist.
+type distCopy struct {
+	// The path to copy from.
+	from Path
+
+	// The destination within the dist directory to copy to.
+	dest string
+}
+
+// Compute the contributions that the module makes to the dist.
+func (a *AndroidMkEntries) getDistContributions(mod blueprint.Module) *distContributions {
 	amod := mod.(Module).base()
 	name := amod.BaseModuleName()
 
-	var ret []string
+	// Collate the set of associated tag/paths available for copying to the dist.
+	// Start with an empty (nil) set.
 	var availableTaggedDists TaggedDistFiles
 
+	// Then merge in any that are provided explicitly by the module.
 	if a.DistFiles != nil {
-		availableTaggedDists = a.DistFiles
-	} else if a.OutputFile.Valid() {
-		availableTaggedDists = MakeDefaultDistFiles(a.OutputFile.Path())
-	} else {
+		// Merge the DistFiles into the set.
+		availableTaggedDists = availableTaggedDists.merge(a.DistFiles)
+	}
+
+	// If no paths have been provided for the DefaultDistTag and the output file is
+	// valid then add that as the default dist path.
+	if _, ok := availableTaggedDists[DefaultDistTag]; !ok && a.OutputFile.Valid() {
+		availableTaggedDists = availableTaggedDists.addPathsForTag(DefaultDistTag, a.OutputFile.Path())
+	}
+
+	// If the distFiles created by GenerateTaggedDistFiles contains paths for the
+	// DefaultDistTag then that takes priority so delete any existing paths.
+	if _, ok := amod.distFiles[DefaultDistTag]; ok {
+		delete(availableTaggedDists, DefaultDistTag)
+	}
+
+	// Finally, merge the distFiles created by GenerateTaggedDistFiles.
+	availableTaggedDists = availableTaggedDists.merge(amod.distFiles)
+
+	if len(availableTaggedDists) == 0 {
 		// Nothing dist-able for this module.
 		return nil
 	}
 
+	// Collate the contributions this module makes to the dist.
+	distContributions := &distContributions{}
+
 	// Iterate over this module's dist structs, merged from the dist and dists properties.
 	for _, dist := range amod.Dists() {
 		// Get the list of goals this dist should be enabled for. e.g. sdk, droidcore
@@ -204,7 +264,7 @@
 		var tag string
 		if dist.Tag == nil {
 			// If the dist struct does not specify a tag, use the default output files tag.
-			tag = ""
+			tag = DefaultDistTag
 		} else {
 			tag = *dist.Tag
 		}
@@ -218,15 +278,15 @@
 		}
 
 		if len(tagPaths) > 1 && (dist.Dest != nil || dist.Suffix != nil) {
-			errorMessage := "Cannot apply dest/suffix for more than one dist " +
-				"file for %s goals in module %s. The list of dist files, " +
+			errorMessage := "%s: Cannot apply dest/suffix for more than one dist " +
+				"file for %q goals tag %q in module %s. The list of dist files, " +
 				"which should have a single element, is:\n%s"
-			panic(fmt.Errorf(errorMessage, goals, name, tagPaths))
+			panic(fmt.Errorf(errorMessage, mod, goals, tag, name, tagPaths))
 		}
 
-		ret = append(ret, fmt.Sprintf(".PHONY: %s\n", goals))
+		copiesForGoals := distContributions.getCopiesForGoals(goals)
 
-		// Create dist-for-goals calls for each path in the dist'd files.
+		// Iterate over each path adding a copy instruction to copiesForGoals
 		for _, path := range tagPaths {
 			// It's possible that the Path is nil from errant modules. Be defensive here.
 			if path == nil {
@@ -261,15 +321,41 @@
 				}
 			}
 
+			copiesForGoals.addCopyInstruction(path, dest)
+		}
+	}
+
+	return distContributions
+}
+
+// generateDistContributionsForMake generates make rules that will generate the
+// dist according to the instructions in the supplied distContribution.
+func generateDistContributionsForMake(distContributions *distContributions) []string {
+	var ret []string
+	for _, d := range distContributions.copiesForGoals {
+		ret = append(ret, fmt.Sprintf(".PHONY: %s\n", d.goals))
+		// Create dist-for-goals calls for each of the copy instructions.
+		for _, c := range d.copies {
 			ret = append(
 				ret,
-				fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", goals, path.String(), dest))
+				fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", d.goals, c.from.String(), c.dest))
 		}
 	}
 
 	return ret
 }
 
+// Compute the list of Make strings to declare phony goals and dist-for-goals
+// calls from the module's dist and dists properties.
+func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
+	distContributions := a.getDistContributions(mod)
+	if distContributions == nil {
+		return nil
+	}
+
+	return generateDistContributionsForMake(distContributions)
+}
+
 func (a *AndroidMkEntries) fillInEntries(config Config, bpPath string, mod blueprint.Module) {
 	a.EntryMap = make(map[string][]string)
 	amod := mod.(Module).base()
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index 80df6a7..347b92e 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -18,17 +18,77 @@
 	"fmt"
 	"io"
 	"reflect"
+	"strings"
 	"testing"
+
+	"github.com/google/blueprint/proptools"
 )
 
 type customModule struct {
 	ModuleBase
-	data      AndroidMkData
-	distFiles TaggedDistFiles
+
+	properties struct {
+		Default_dist_files *string
+		Dist_output_file   *bool
+	}
+
+	data       AndroidMkData
+	distFiles  TaggedDistFiles
+	outputFile OptionalPath
+
+	// The paths that will be used as the default dist paths if no tag is
+	// specified.
+	defaultDistPaths Paths
 }
 
+const (
+	defaultDistFiles_None    = "none"
+	defaultDistFiles_Default = "default"
+	defaultDistFiles_Tagged  = "tagged"
+)
+
 func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	m.distFiles = m.GenerateTaggedDistFiles(ctx)
+
+	// If the dist_output_file: true then create an output file that is stored in
+	// the OutputFile property of the AndroidMkEntry.
+	if proptools.BoolDefault(m.properties.Dist_output_file, true) {
+		path := PathForTesting("dist-output-file.out")
+		m.outputFile = OptionalPathForPath(path)
+
+		// Previous code would prioritize the DistFiles property over the OutputFile
+		// property in AndroidMkEntry when determining the default dist paths.
+		// Setting this first allows it to be overridden based on the
+		// default_dist_files setting replicating that previous behavior.
+		m.defaultDistPaths = Paths{path}
+	}
+
+	// Based on the setting of the default_dist_files property possibly create a
+	// TaggedDistFiles structure that will be stored in the DistFiles property of
+	// the AndroidMkEntry.
+	defaultDistFiles := proptools.StringDefault(m.properties.Default_dist_files, defaultDistFiles_Tagged)
+	switch defaultDistFiles {
+	case defaultDistFiles_None:
+		// Do nothing
+
+	case defaultDistFiles_Default:
+		path := PathForTesting("default-dist.out")
+		m.defaultDistPaths = Paths{path}
+		m.distFiles = MakeDefaultDistFiles(path)
+
+	case defaultDistFiles_Tagged:
+		// Module types that set AndroidMkEntry.DistFiles to the result of calling
+		// GenerateTaggedDistFiles(ctx) relied on no tag being treated as "" which
+		// meant that the default dist paths would be whatever was returned by
+		// OutputFiles(""). In order to preserve that behavior when treating no tag
+		// as being equal to DefaultDistTag this ensures that
+		// OutputFiles(DefaultDistTag) will return the same as OutputFiles("").
+		m.defaultDistPaths = PathsForTesting("one.out")
+
+		// This must be called after setting defaultDistPaths/outputFile as
+		// GenerateTaggedDistFiles calls into OutputFiles(tag) which may use those
+		// fields.
+		m.distFiles = m.GenerateTaggedDistFiles(ctx)
+	}
 }
 
 func (m *customModule) AndroidMk() AndroidMkData {
@@ -41,6 +101,12 @@
 
 func (m *customModule) OutputFiles(tag string) (Paths, error) {
 	switch tag {
+	case DefaultDistTag:
+		if m.defaultDistPaths != nil {
+			return m.defaultDistPaths, nil
+		} else {
+			return nil, fmt.Errorf("default dist tag is not available")
+		}
 	case "":
 		return PathsForTesting("one.out"), nil
 	case ".multiple":
@@ -55,28 +121,26 @@
 func (m *customModule) AndroidMkEntries() []AndroidMkEntries {
 	return []AndroidMkEntries{
 		{
-			Class:     "CUSTOM_MODULE",
-			DistFiles: m.distFiles,
+			Class:      "CUSTOM_MODULE",
+			DistFiles:  m.distFiles,
+			OutputFile: m.outputFile,
 		},
 	}
 }
 
 func customModuleFactory() Module {
 	module := &customModule{}
+
+	module.AddProperties(&module.properties)
+
 	InitAndroidModule(module)
 	return module
 }
 
-func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
-	bp := `
-	custom {
-		name: "foo",
-		required: ["bar"],
-		host_required: ["baz"],
-		target_required: ["qux"],
-	}
-	`
-
+// buildConfigAndCustomModuleFoo creates a config object, processes the supplied
+// bp module and then returns the config and the custom module called "foo".
+func buildConfigAndCustomModuleFoo(t *testing.T, bp string) (Config, *customModule) {
+	t.Helper()
 	config := TestConfig(buildDir, nil, bp, nil)
 	config.katiEnabled = true // Enable androidmk Singleton
 
@@ -90,7 +154,21 @@
 	_, errs = ctx.PrepareBuildActions(config)
 	FailIfErrored(t, errs)
 
-	m := ctx.ModuleForTests("foo", "").Module().(*customModule)
+	module := ctx.ModuleForTests("foo", "").Module().(*customModule)
+	return config, module
+}
+
+func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
+	bp := `
+	custom {
+		name: "foo",
+		required: ["bar"],
+		host_required: ["baz"],
+		target_required: ["qux"],
+	}
+	`
+
+	_, m := buildConfigAndCustomModuleFoo(t, bp)
 
 	assertEqual := func(expected interface{}, actual interface{}) {
 		if !reflect.DeepEqual(expected, actual) {
@@ -102,108 +180,29 @@
 	assertEqual([]string{"qux"}, m.data.Target_required)
 }
 
-func TestGetDistForGoals(t *testing.T) {
-	testCases := []struct {
-		name                   string
-		bp                     string
-		expectedAndroidMkLines []string
-	}{
-		{
-			name: "dist-without-tag",
-			bp: `
-			custom {
-				name: "foo",
-				dist: {
-					targets: ["my_goal"]
-				}
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
-			},
-		},
-		{
-			name: "dist-with-tag",
-			bp: `
-			custom {
-				name: "foo",
-				dist: {
-					targets: ["my_goal"],
-					tag: ".another-tag",
-				}
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,another.out:another.out)\n",
-			},
-		},
-		{
-			name: "dists-with-tag",
-			bp: `
-			custom {
-				name: "foo",
-				dists: [
-					{
-						targets: ["my_goal"],
-						tag: ".another-tag",
-					},
-				],
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,another.out:another.out)\n",
-			},
-		},
-		{
-			name: "multiple-dists-with-and-without-tag",
-			bp: `
-			custom {
-				name: "foo",
-				dists: [
-					{
-						targets: ["my_goal"],
-					},
-					{
-						targets: ["my_second_goal", "my_third_goal"],
-					},
-				],
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
-				".PHONY: my_second_goal my_third_goal\n",
-				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
-			},
-		},
-		{
-			name: "dist-plus-dists-without-tags",
-			bp: `
-			custom {
-				name: "foo",
-				dist: {
-					targets: ["my_goal"],
+func TestGenerateDistContributionsForMake(t *testing.T) {
+	dc := &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one.out"),
+					distCopyForTest("two.out", "other.out"),
 				},
-				dists: [
-					{
-						targets: ["my_second_goal", "my_third_goal"],
-					},
-				],
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_second_goal my_third_goal\n",
-				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
 			},
 		},
-		{
-			name: "dist-plus-dists-with-tags",
-			bp: `
+	}
+
+	makeOutput := generateDistContributionsForMake(dc)
+
+	assertStringEquals(t, `.PHONY: my_goal
+$(call dist-for-goals,my_goal,one.out:one.out)
+$(call dist-for-goals,my_goal,two.out:other.out)
+`, strings.Join(makeOutput, ""))
+}
+
+func TestGetDistForGoals(t *testing.T) {
+	bp := `
 			custom {
 				name: "foo",
 				dist: {
@@ -235,66 +234,522 @@
 					},
 				],
 			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_second_goal\n",
-				"$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
-				"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
-				".PHONY: my_third_goal\n",
-				"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
-				".PHONY: my_fourth_goal\n",
-				"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
-				".PHONY: my_fifth_goal\n",
-				"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
-				".PHONY: my_sixth_goal\n",
-				"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
-				".PHONY: my_goal my_other_goal\n",
-				"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
-				"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
-			},
-		},
+			`
+
+	expectedAndroidMkLines := []string{
+		".PHONY: my_second_goal\n",
+		"$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
+		"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
+		".PHONY: my_third_goal\n",
+		"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
+		".PHONY: my_fourth_goal\n",
+		"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
+		".PHONY: my_fifth_goal\n",
+		"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
+		".PHONY: my_sixth_goal\n",
+		"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
+		".PHONY: my_goal my_other_goal\n",
+		"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
+		"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
 	}
 
-	for _, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			config := TestConfig(buildDir, nil, testCase.bp, nil)
-			config.katiEnabled = true // Enable androidmk Singleton
+	config, module := buildConfigAndCustomModuleFoo(t, bp)
+	entries := AndroidMkEntriesForTest(t, config, "", module)
+	if len(entries) != 1 {
+		t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
+	}
+	androidMkLines := entries[0].GetDistForGoals(module)
 
-			ctx := NewTestContext(config)
-			ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
-			ctx.RegisterModuleType("custom", customModuleFactory)
-			ctx.Register()
+	if len(androidMkLines) != len(expectedAndroidMkLines) {
+		t.Errorf(
+			"Expected %d AndroidMk lines, got %d:\n%v",
+			len(expectedAndroidMkLines),
+			len(androidMkLines),
+			androidMkLines,
+		)
+	}
+	for idx, line := range androidMkLines {
+		expectedLine := expectedAndroidMkLines[idx]
+		if line != expectedLine {
+			t.Errorf(
+				"Expected AndroidMk line to be '%s', got '%s'",
+				expectedLine,
+				line,
+			)
+		}
+	}
+}
 
-			_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-			FailIfErrored(t, errs)
-			_, errs = ctx.PrepareBuildActions(config)
-			FailIfErrored(t, errs)
+func distCopyForTest(from, to string) distCopy {
+	return distCopy{PathForTesting(from), to}
+}
 
-			module := ctx.ModuleForTests("foo", "").Module().(*customModule)
+func TestGetDistContributions(t *testing.T) {
+	compareContributions := func(d1 *distContributions, d2 *distContributions) error {
+		if d1 == nil || d2 == nil {
+			if d1 != d2 {
+				return fmt.Errorf("pointer mismatch, expected both to be nil but they were %p and %p", d1, d2)
+			} else {
+				return nil
+			}
+		}
+		if expected, actual := len(d1.copiesForGoals), len(d2.copiesForGoals); expected != actual {
+			return fmt.Errorf("length mismatch, expected %d found %d", expected, actual)
+		}
+
+		for i, copies1 := range d1.copiesForGoals {
+			copies2 := d2.copiesForGoals[i]
+			if expected, actual := copies1.goals, copies2.goals; expected != actual {
+				return fmt.Errorf("goals mismatch at position %d: expected %q found %q", i, expected, actual)
+			}
+
+			if expected, actual := len(copies1.copies), len(copies2.copies); expected != actual {
+				return fmt.Errorf("length mismatch in copy instructions at position %d, expected %d found %d", i, expected, actual)
+			}
+
+			for j, c1 := range copies1.copies {
+				c2 := copies2.copies[j]
+				if expected, actual := NormalizePathForTesting(c1.from), NormalizePathForTesting(c2.from); expected != actual {
+					return fmt.Errorf("paths mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
+				}
+
+				if expected, actual := c1.dest, c2.dest; expected != actual {
+					return fmt.Errorf("dest mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
+				}
+			}
+		}
+
+		return nil
+	}
+
+	formatContributions := func(d *distContributions) string {
+		buf := &strings.Builder{}
+		if d == nil {
+			fmt.Fprint(buf, "nil")
+		} else {
+			for _, copiesForGoals := range d.copiesForGoals {
+				fmt.Fprintf(buf, "    Goals: %q {\n", copiesForGoals.goals)
+				for _, c := range copiesForGoals.copies {
+					fmt.Fprintf(buf, "        %s -> %s\n", NormalizePathForTesting(c.from), c.dest)
+				}
+				fmt.Fprint(buf, "    }\n")
+			}
+		}
+		return buf.String()
+	}
+
+	testHelper := func(t *testing.T, name, bp string, expectedContributions *distContributions) {
+		t.Helper()
+		t.Run(name, func(t *testing.T) {
+			t.Helper()
+
+			config, module := buildConfigAndCustomModuleFoo(t, bp)
 			entries := AndroidMkEntriesForTest(t, config, "", module)
 			if len(entries) != 1 {
 				t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
 			}
-			androidMkLines := entries[0].GetDistForGoals(module)
+			distContributions := entries[0].getDistContributions(module)
 
-			if len(androidMkLines) != len(testCase.expectedAndroidMkLines) {
-				t.Errorf(
-					"Expected %d AndroidMk lines, got %d:\n%v",
-					len(testCase.expectedAndroidMkLines),
-					len(androidMkLines),
-					androidMkLines,
-				)
-			}
-			for idx, line := range androidMkLines {
-				expectedLine := testCase.expectedAndroidMkLines[idx]
-				if line != expectedLine {
-					t.Errorf(
-						"Expected AndroidMk line to be '%s', got '%s'",
-						expectedLine,
-						line,
-					)
-				}
+			if err := compareContributions(expectedContributions, distContributions); err != nil {
+				t.Errorf("%s\nExpected Contributions\n%sActualContributions\n%s",
+					err,
+					formatContributions(expectedContributions),
+					formatContributions(distContributions))
 			}
 		})
 	}
+
+	testHelper(t, "dist-without-tag", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"]
+				}
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dist-with-tag", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"],
+					tag: ".another-tag",
+				}
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("another.out", "another.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dists-with-tag", `
+			custom {
+				name: "foo",
+				dists: [
+					{
+						targets: ["my_goal"],
+						tag: ".another-tag",
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("another.out", "another.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "multiple-dists-with-and-without-tag", `
+			custom {
+				name: "foo",
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_second_goal", "my_third_goal"],
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+				{
+					goals: "my_second_goal my_third_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dist-plus-dists-without-tags", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"],
+				},
+				dists: [
+					{
+						targets: ["my_second_goal", "my_third_goal"],
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_second_goal my_third_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dist-plus-dists-with-tags", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal", "my_other_goal"],
+					tag: ".multiple",
+				},
+				dists: [
+					{
+						targets: ["my_second_goal"],
+						tag: ".multiple",
+					},
+					{
+						targets: ["my_third_goal"],
+						dir: "test/dir",
+					},
+					{
+						targets: ["my_fourth_goal"],
+						suffix: ".suffix",
+					},
+					{
+						targets: ["my_fifth_goal"],
+						dest: "new-name",
+					},
+					{
+						targets: ["my_sixth_goal"],
+						dest: "new-name",
+						dir: "some/dir",
+						suffix: ".suffix",
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_second_goal",
+					copies: []distCopy{
+						distCopyForTest("two.out", "two.out"),
+						distCopyForTest("three/four.out", "four.out"),
+					},
+				},
+				{
+					goals: "my_third_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "test/dir/one.out"),
+					},
+				},
+				{
+					goals: "my_fourth_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.suffix.out"),
+					},
+				},
+				{
+					goals: "my_fifth_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "new-name"),
+					},
+				},
+				{
+					goals: "my_sixth_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "some/dir/new-name.suffix"),
+					},
+				},
+				{
+					goals: "my_goal my_other_goal",
+					copies: []distCopy{
+						distCopyForTest("two.out", "two.out"),
+						distCopyForTest("three/four.out", "four.out"),
+					},
+				},
+			},
+		})
+
+	// The above test the default values of default_dist_files and use_output_file.
+
+	// The following tests explicitly test the different combinations of those settings.
+	testHelper(t, "tagged-dist-files-no-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "tagged",
+				dist_output_file: false,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "default-dist-files-no-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "default",
+				dist_output_file: false,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("default-dist.out", "default-dist.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "no-dist-files-no-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "none",
+				dist_output_file: false,
+				dists: [
+					// The following is silently ignored because there is not default file
+					// in either the dist files or the output file.
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "tagged-dist-files-default-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "tagged",
+				dist_output_file: true,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "default-dist-files-default-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "default",
+				dist_output_file: true,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("default-dist.out", "default-dist.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "no-dist-files-default-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "none",
+				dist_output_file: true,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("dist-output-file.out", "dist-output-file.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
 }
diff --git a/android/module.go b/android/module.go
index bd60829..d680baa 100644
--- a/android/module.go
+++ b/android/module.go
@@ -503,8 +503,12 @@
 	// A suffix to add to the artifact file name (before any extension).
 	Suffix *string `android:"arch_variant"`
 
-	// A string tag to select the OutputFiles associated with the tag. Defaults to the
-	// the empty "" string.
+	// A string tag to select the OutputFiles associated with the tag.
+	//
+	// If no tag is specified then it will select the default dist paths provided
+	// by the module type. If a tag of "" is specified then it will return the
+	// default output files provided by the modules, i.e. the result of calling
+	// OutputFiles("").
 	Tag *string `android:"arch_variant"`
 }
 
@@ -733,9 +737,45 @@
 	Dists []Dist `android:"arch_variant"`
 }
 
+// The key to use in TaggedDistFiles when a Dist structure does not specify a
+// tag property. This intentionally does not use "" as the default because that
+// would mean that an empty tag would have a different meaning when used in a dist
+// structure that when used to reference a specific set of output paths using the
+// :module{tag} syntax, which passes tag to the OutputFiles(tag) method.
+const DefaultDistTag = "<default-dist-tag>"
+
 // A map of OutputFile tag keys to Paths, for disting purposes.
 type TaggedDistFiles map[string]Paths
 
+// addPathsForTag adds a mapping from the tag to the paths. If the map is nil
+// then it will create a map, update it and then return it. If a mapping already
+// exists for the tag then the paths are appended to the end of the current list
+// of paths, ignoring any duplicates.
+func (t TaggedDistFiles) addPathsForTag(tag string, paths ...Path) TaggedDistFiles {
+	if t == nil {
+		t = make(TaggedDistFiles)
+	}
+
+	for _, distFile := range paths {
+		if distFile != nil && !t[tag].containsPath(distFile) {
+			t[tag] = append(t[tag], distFile)
+		}
+	}
+
+	return t
+}
+
+// merge merges the entries from the other TaggedDistFiles object into this one.
+// If the TaggedDistFiles is nil then it will create a new instance, merge the
+// other into it, and then return it.
+func (t TaggedDistFiles) merge(other TaggedDistFiles) TaggedDistFiles {
+	for tag, paths := range other {
+		t = t.addPathsForTag(tag, paths...)
+	}
+
+	return t
+}
+
 func MakeDefaultDistFiles(paths ...Path) TaggedDistFiles {
 	for _, path := range paths {
 		if path == nil {
@@ -744,7 +784,7 @@
 	}
 
 	// The default OutputFile tag is the empty "" string.
-	return TaggedDistFiles{"": paths}
+	return TaggedDistFiles{DefaultDistTag: paths}
 }
 
 type hostAndDeviceProperties struct {
@@ -970,6 +1010,9 @@
 	noticeFiles        Paths
 	phonies            map[string]Paths
 
+	// The files to copy to the dist as explicitly specified in the .bp file.
+	distFiles TaggedDistFiles
+
 	// Used by buildTargetSingleton to create checkbuild and per-directory build targets
 	// Only set on the final variant of each module
 	installTarget    WritablePath
@@ -1071,23 +1114,30 @@
 }
 
 func (m *ModuleBase) GenerateTaggedDistFiles(ctx BaseModuleContext) TaggedDistFiles {
-	distFiles := make(TaggedDistFiles)
+	var distFiles TaggedDistFiles
 	for _, dist := range m.Dists() {
-		var tag string
-		var distFilesForTag Paths
-		if dist.Tag == nil {
-			tag = ""
-		} else {
-			tag = *dist.Tag
-		}
-		distFilesForTag, err := m.base().module.(OutputFileProducer).OutputFiles(tag)
-		if err != nil {
-			ctx.PropertyErrorf("dist.tag", "%s", err.Error())
-		}
-		for _, distFile := range distFilesForTag {
-			if distFile != nil && !distFiles[tag].containsPath(distFile) {
-				distFiles[tag] = append(distFiles[tag], distFile)
+		// If no tag is specified then it means to use the default dist paths so use
+		// the special tag name which represents that.
+		tag := proptools.StringDefault(dist.Tag, DefaultDistTag)
+
+		if outputFileProducer, ok := m.module.(OutputFileProducer); ok {
+			// Call the OutputFiles(tag) method to get the paths associated with the tag.
+			distFilesForTag, err := outputFileProducer.OutputFiles(tag)
+
+			// If the tag was not supported and is not DefaultDistTag then it is an error.
+			// Failing to find paths for DefaultDistTag is not an error. It just means
+			// that the module type requires the legacy behavior.
+			if err != nil && tag != DefaultDistTag {
+				ctx.PropertyErrorf("dist.tag", "%s", err.Error())
 			}
+
+			distFiles = distFiles.addPathsForTag(tag, distFilesForTag...)
+		} else if tag != DefaultDistTag {
+			// If the tag was specified then it is an error if the module does not
+			// implement OutputFileProducer because there is no other way of accessing
+			// the paths for the specified tag.
+			ctx.PropertyErrorf("dist.tag",
+				"tag %s not supported because the module does not implement OutputFileProducer", tag)
 		}
 	}
 
@@ -1579,22 +1629,9 @@
 	ctx.Variable(pctx, "moduleDescSuffix", s)
 
 	// Some common property checks for properties that will be used later in androidmk.go
-	if m.distProperties.Dist.Dest != nil {
-		_, err := validateSafePath(*m.distProperties.Dist.Dest)
-		if err != nil {
-			ctx.PropertyErrorf("dist.dest", "%s", err.Error())
-		}
-	}
-	if m.distProperties.Dist.Dir != nil {
-		_, err := validateSafePath(*m.distProperties.Dist.Dir)
-		if err != nil {
-			ctx.PropertyErrorf("dist.dir", "%s", err.Error())
-		}
-	}
-	if m.distProperties.Dist.Suffix != nil {
-		if strings.Contains(*m.distProperties.Dist.Suffix, "/") {
-			ctx.PropertyErrorf("dist.suffix", "Suffix may not contain a '/' character.")
-		}
+	checkDistProperties(ctx, "dist", &m.distProperties.Dist)
+	for i, _ := range m.distProperties.Dists {
+		checkDistProperties(ctx, fmt.Sprintf("dists[%d]", i), &m.distProperties.Dists[i])
 	}
 
 	if m.Enabled() {
@@ -1631,6 +1668,15 @@
 			return
 		}
 
+		// Create the set of tagged dist files after calling GenerateAndroidBuildActions
+		// as GenerateTaggedDistFiles() calls OutputFiles(tag) and so relies on the
+		// output paths being set which must be done before or during
+		// GenerateAndroidBuildActions.
+		m.distFiles = m.GenerateTaggedDistFiles(ctx)
+		if ctx.Failed() {
+			return
+		}
+
 		m.installFiles = append(m.installFiles, ctx.installFiles...)
 		m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...)
 		m.packagingSpecs = append(m.packagingSpecs, ctx.packagingSpecs...)
@@ -1659,6 +1705,32 @@
 	m.variables = ctx.variables
 }
 
+// Check the supplied dist structure to make sure that it is valid.
+//
+// property - the base property, e.g. dist or dists[1], which is combined with the
+// name of the nested property to produce the full property, e.g. dist.dest or
+// dists[1].dir.
+func checkDistProperties(ctx *moduleContext, property string, dist *Dist) {
+	if dist.Dest != nil {
+		_, err := validateSafePath(*dist.Dest)
+		if err != nil {
+			ctx.PropertyErrorf(property+".dest", "%s", err.Error())
+		}
+	}
+	if dist.Dir != nil {
+		_, err := validateSafePath(*dist.Dir)
+		if err != nil {
+			ctx.PropertyErrorf(property+".dir", "%s", err.Error())
+		}
+	}
+	if dist.Suffix != nil {
+		if strings.Contains(*dist.Suffix, "/") {
+			ctx.PropertyErrorf(property+".suffix", "Suffix may not contain a '/' character.")
+		}
+	}
+
+}
+
 type earlyModuleContext struct {
 	blueprint.EarlyModuleContext
 
diff --git a/android/module_test.go b/android/module_test.go
index 6cc1813..e3cc613 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -232,3 +232,51 @@
 		t.Errorf("Expected build params to fail validation: %+v", bparams)
 	}
 }
+
+func TestDistErrorChecking(t *testing.T) {
+	bp := `
+		deps {
+			name: "foo",
+      dist: {
+        dest: "../invalid-dest",
+        dir: "../invalid-dir",
+        suffix: "invalid/suffix",
+      },
+      dists: [
+        {
+          dest: "../invalid-dest0",
+          dir: "../invalid-dir0",
+          suffix: "invalid/suffix0",
+        },
+        {
+          dest: "../invalid-dest1",
+          dir: "../invalid-dir1",
+          suffix: "invalid/suffix1",
+        },
+      ],
+ 		}
+	`
+
+	config := TestConfig(buildDir, nil, bp, nil)
+
+	ctx := NewTestContext(config)
+	ctx.RegisterModuleType("deps", depsModuleFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+
+	expectedErrs := []string{
+		"\\QAndroid.bp:5:13: module \"foo\": dist.dest: Path is outside directory: ../invalid-dest\\E",
+		"\\QAndroid.bp:6:12: module \"foo\": dist.dir: Path is outside directory: ../invalid-dir\\E",
+		"\\QAndroid.bp:7:15: module \"foo\": dist.suffix: Suffix may not contain a '/' character.\\E",
+		"\\QAndroid.bp:11:15: module \"foo\": dists[0].dest: Path is outside directory: ../invalid-dest0\\E",
+		"\\QAndroid.bp:12:14: module \"foo\": dists[0].dir: Path is outside directory: ../invalid-dir0\\E",
+		"\\QAndroid.bp:13:17: module \"foo\": dists[0].suffix: Suffix may not contain a '/' character.\\E",
+		"\\QAndroid.bp:16:15: module \"foo\": dists[1].dest: Path is outside directory: ../invalid-dest1\\E",
+		"\\QAndroid.bp:17:14: module \"foo\": dists[1].dir: Path is outside directory: ../invalid-dir1\\E",
+		"\\QAndroid.bp:18:17: module \"foo\": dists[1].suffix: Suffix may not contain a '/' character.\\E",
+	}
+	CheckErrorsAgainstExpectations(t, errs, expectedErrs)
+}
diff --git a/apex/androidmk.go b/apex/androidmk.go
index fe89b73..da38c2a 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -334,7 +334,6 @@
 
 func (a *apexBundle) androidMkForType() android.AndroidMkData {
 	return android.AndroidMkData{
-		DistFiles: a.distFiles,
 		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
 			moduleNames := []string{}
 			apexType := a.properties.ApexType
diff --git a/apex/apex.go b/apex/apex.go
index fceb307..f127757 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -353,8 +353,6 @@
 	lintReports android.Paths
 
 	prebuiltFileToDelete string
-
-	distFiles android.TaggedDistFiles
 }
 
 // apexFileClass represents a type of file that can be included in APEX.
@@ -1103,7 +1101,8 @@
 // Implements android.OutputFileProducer
 func (a *apexBundle) OutputFiles(tag string) (android.Paths, error) {
 	switch tag {
-	case "":
+	case "", android.DefaultDistTag:
+		// This is the default dist path.
 		return android.Paths{a.outputFile}, nil
 	default:
 		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
@@ -1832,14 +1831,11 @@
 		a.linkToSystemLib = false
 	}
 
-	a.setCertificateAndPrivateKey(ctx)
-
 	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// 4) generate the build rules to create the APEX. This is done in builder.go.
 	a.buildManifest(ctx, provideNativeLibs, requireNativeLibs)
-	a.buildFileContexts(ctx)
 	if a.properties.ApexType == flattenedApex {
 		a.buildFlattenedApex(ctx)
 	} else {
@@ -1847,7 +1843,25 @@
 	}
 	a.buildApexDependencyInfo(ctx)
 	a.buildLintReports(ctx)
-	a.distFiles = a.GenerateTaggedDistFiles(ctx)
+
+	// Append meta-files to the filesInfo list so that they are reflected in Android.mk as well.
+	if a.installable() {
+		// For flattened APEX, make sure that APEX manifest and apex_pubkey are also copied
+		// along with other ordinary files. (Note that this is done by apexer for
+		// non-flattened APEXes)
+		a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil))
+
+		// Place the public key as apex_pubkey. This is also done by apexer for
+		// non-flattened APEXes case.
+		// TODO(jiyong): Why do we need this CP rule?
+		copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  a.public_key_file,
+			Output: copiedPubkey,
+		})
+		a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil))
+	}
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/apex/builder.go b/apex/builder.go
index 6f27dd1..b858135 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -40,7 +40,7 @@
 	pctx.Import("android/soong/java")
 	pctx.HostBinToolVariable("apexer", "apexer")
 	// ART minimal builds (using the master-art manifest) do not have the "frameworks/base"
-	// projects, and hence cannot built 'aapt2'. Use the SDK prebuilt instead.
+	// projects, and hence cannot build 'aapt2'. Use the SDK prebuilt instead.
 	hostBinToolVariableWithPrebuilt := func(name, prebuiltDir, tool string) {
 		pctx.VariableFunc(name, func(ctx android.PackageVarContext) string {
 			if !ctx.Config().FrameworksBaseDirExists(ctx) {
@@ -175,24 +175,29 @@
 			`exit 1); touch ${out}`,
 		Description: "Diff ${image_content_file} and ${allowed_files_file}",
 	}, "image_content_file", "allowed_files_file", "apex_module_name")
+
+	// Don't add more rules here. Consider using android.NewRuleBuilder instead.
 )
 
+// buildManifest creates buile rules to modify the input apex_manifest.json to add information
+// gathered by the build system such as provided/required native libraries. Two output files having
+// different formats are generated. a.manifestJsonOut is JSON format for Q devices, and
+// a.manifest.PbOut is protobuf format for R+ devices.
+// TODO(jiyong): make this to return paths instead of directly storing the paths to apexBundle
 func (a *apexBundle) buildManifest(ctx android.ModuleContext, provideNativeLibs, requireNativeLibs []string) {
-	manifestSrc := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
+	src := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
 
-	manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
-
-	// put dependency({provide|require}NativeLibs) in apex_manifest.json
+	// Put dependency({provide|require}NativeLibs) in apex_manifest.json
 	provideNativeLibs = android.SortedUniqueStrings(provideNativeLibs)
 	requireNativeLibs = android.SortedUniqueStrings(android.RemoveListFromList(requireNativeLibs, provideNativeLibs))
 
-	// apex name can be overridden
+	// APEX name can be overridden
 	optCommands := []string{}
 	if a.properties.Apex_name != nil {
 		optCommands = append(optCommands, "-v name "+*a.properties.Apex_name)
 	}
 
-	// collect jniLibs. Notice that a.filesInfo is already sorted
+	// Collect jniLibs. Notice that a.filesInfo is already sorted
 	var jniLibs []string
 	for _, fi := range a.filesInfo {
 		if fi.isJniLib && !android.InList(fi.stem(), jniLibs) {
@@ -203,9 +208,10 @@
 		optCommands = append(optCommands, "-a jniLibs "+strings.Join(jniLibs, " "))
 	}
 
+	manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexManifestRule,
-		Input:  manifestSrc,
+		Input:  src,
 		Output: manifestJsonFullOut,
 		Args: map[string]string{
 			"provideNativeLibs": strings.Join(provideNativeLibs, " "),
@@ -214,10 +220,10 @@
 		},
 	})
 
+	// b/143654022 Q apexd can't understand newly added keys in apex_manifest.json prepare
+	// stripped-down version so that APEX modules built from R+ can be installed to Q
 	minSdkVersion := a.minSdkVersion(ctx)
 	if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
-		// b/143654022 Q apexd can't understand newly added keys in apex_manifest.json
-		// prepare stripped-down version so that APEX modules built from R+ can be installed to Q
 		a.manifestJsonOut = android.PathForModuleOut(ctx, "apex_manifest.json")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   stripApexManifestRule,
@@ -226,7 +232,7 @@
 		})
 	}
 
-	// from R+, protobuf binary format (.pb) is the standard format for apex_manifest
+	// From R+, protobuf binary format (.pb) is the standard format for apex_manifest
 	a.manifestPbOut = android.PathForModuleOut(ctx, "apex_manifest.pb")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   pbApexManifestRule,
@@ -235,10 +241,11 @@
 	})
 }
 
-func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) {
-	if a.properties.ApexType == zipApex {
-		return
-	}
+// buildFileContexts create build rules to append an entry for apex_manifest.pb to the file_contexts
+// file for this APEX which is either from /systme/sepolicy/apex/<apexname>-file_contexts or from
+// the file_contexts property of this APEX. This is to make sure that the manifest file is correctly
+// labeled as system_file.
+func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
 	var fileContexts android.Path
 	if a.properties.File_contexts == nil {
 		fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
@@ -248,18 +255,17 @@
 	if a.Platform() {
 		if matched, err := path.Match("system/sepolicy/**/*", fileContexts.String()); err != nil || !matched {
 			ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", fileContexts)
-			return
 		}
 	}
 	if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() {
-		ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", a.fileContexts)
-		return
+		ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", fileContexts.String())
 	}
 
 	output := android.PathForModuleOut(ctx, "file_contexts")
 	rule := android.NewRuleBuilder()
 
-	if a.properties.ApexType == imageApex {
+	switch a.properties.ApexType {
+	case imageApex:
 		// remove old file
 		rule.Command().Text("rm").FlagWithOutput("-f ", output)
 		// copy file_contexts
@@ -269,7 +275,7 @@
 		// force-label /apex_manifest.pb and / as system_file so that apexd can read them
 		rule.Command().Text("echo").Flag("/apex_manifest\\\\.pb u:object_r:system_file:s0").Text(">>").Output(output)
 		rule.Command().Text("echo").Flag("/ u:object_r:system_file:s0").Text(">>").Output(output)
-	} else {
+	case flattenedApex:
 		// For flattened apexes, install path should be prepended.
 		// File_contexts file should be emiited to make via LOCAL_FILE_CONTEXTS
 		// so that it can be merged into file_contexts.bin
@@ -284,13 +290,16 @@
 		// force-label /apex_manifest.pb and / as system_file so that apexd can read them
 		rule.Command().Text("echo").Flag(apexPath + `/apex_manifest\\.pb u:object_r:system_file:s0`).Text(">>").Output(output)
 		rule.Command().Text("echo").Flag(apexPath + "/ u:object_r:system_file:s0").Text(">>").Output(output)
+	default:
+		panic(fmt.Errorf("unsupported type %v", a.properties.ApexType))
 	}
 
 	rule.Build(pctx, ctx, "file_contexts."+a.Name(), "Generate file_contexts")
-
-	a.fileContexts = output.OutputPath
+	return output.OutputPath
 }
 
+// buildNoticeFiles creates a buile rule for aggregating notice files from the modules that
+// contributes to this APEX. The notice files are merged into a big notice file.
 func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName string) android.NoticeOutputs {
 	var noticeFiles android.Paths
 
@@ -299,13 +308,11 @@
 			// As soon as the dependency graph crosses the APEX boundary, don't go further.
 			return false
 		}
-
-		notices := to.NoticeFiles()
-		noticeFiles = append(noticeFiles, notices...)
-
+		noticeFiles = append(noticeFiles, to.NoticeFiles()...)
 		return true
 	})
 
+	// TODO(jiyong): why do we need this? WalkPayloadDeps should have already covered this.
 	for _, fi := range a.filesInfo {
 		noticeFiles = append(noticeFiles, fi.noticeFiles...)
 	}
@@ -317,6 +324,9 @@
 	return android.BuildNoticeOutput(ctx, a.installDir, apexFileName, android.SortedUniquePaths(noticeFiles))
 }
 
+// buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
+// files included in this APEX is shown. The text file is dist'ed so that people can see what's
+// included in the APEX without actually downloading and extracting it.
 func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.OutputPath {
 	output := android.PathForModuleOut(ctx, "installed-files.txt")
 	rule := android.NewRuleBuilder()
@@ -330,6 +340,8 @@
 	return output.OutputPath
 }
 
+// buildBundleConfig creates a build rule for the bundle config file that will control the bundle
+// creation process.
 func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.OutputPath {
 	output := android.PathForModuleOut(ctx, "bundle_config.json")
 
@@ -351,8 +363,8 @@
 		"apex_manifest.*",
 	}
 
-	// collect the manifest names and paths of android apps
-	// if their manifest names are overridden
+	// Collect the manifest names and paths of android apps if their manifest names are
+	// overridden.
 	for _, fi := range a.filesInfo {
 		if fi.class != app && fi.class != appSet {
 			continue
@@ -378,30 +390,31 @@
 	return output.OutputPath
 }
 
+// buildUnflattendApex creates build rules to build an APEX using apexer.
 func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) {
-	var abis []string
-	for _, target := range ctx.MultiTargets() {
-		if len(target.Arch.Abi) > 0 {
-			abis = append(abis, target.Arch.Abi[0])
-		}
-	}
-
-	abis = android.FirstUniqueStrings(abis)
-
 	apexType := a.properties.ApexType
 	suffix := apexType.suffix()
-	var implicitInputs []android.Path
-	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
 
-	// TODO(jiyong): construct the copy rules using RuleBuilder
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// Step 1: copy built files to appropriate directories under the image directory
+
+	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
+
+	// TODO(jiyong): use the RuleBuilder
 	var copyCommands []string
+	var implicitInputs []android.Path
 	for _, fi := range a.filesInfo {
-		destPath := android.PathForModuleOut(ctx, "image"+suffix, fi.path()).String()
+		destPath := imageDir.Join(ctx, fi.path()).String()
+
+		// Prepare the destination path
 		destPathDir := filepath.Dir(destPath)
 		if fi.class == appSet {
 			copyCommands = append(copyCommands, "rm -rf "+destPathDir)
 		}
 		copyCommands = append(copyCommands, "mkdir -p "+destPathDir)
+
+		// Copy the built file to the directory. But if the symlink optimization is turned
+		// on, place a symlink to the corresponding file in /system partition instead.
 		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
 			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
 			pathOnDevice := filepath.Join("/system", fi.path())
@@ -415,11 +428,15 @@
 			}
 			implicitInputs = append(implicitInputs, fi.builtFile)
 		}
-		// create additional symlinks pointing the file inside the APEX
+
+		// Create additional symlinks pointing the file inside the APEX (if any). Note that
+		// this is independent from the symlink optimization.
 		for _, symlinkPath := range fi.symlinkPaths() {
-			symlinkDest := android.PathForModuleOut(ctx, "image"+suffix, symlinkPath).String()
+			symlinkDest := imageDir.Join(ctx, symlinkPath).String()
 			copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
 		}
+
+		// Copy the test files (if any)
 		for _, d := range fi.dataPaths {
 			// TODO(eakammer): This is now the third repetition of ~this logic for test paths, refactoring should be possible
 			relPath := d.SrcPath.Rel()
@@ -428,28 +445,37 @@
 				panic(fmt.Errorf("path %q does not end with %q", dataPath, relPath))
 			}
 
-			dataDest := android.PathForModuleOut(ctx, "image"+suffix, fi.apexRelativePath(relPath), d.RelativeInstallPath).String()
+			dataDest := imageDir.Join(ctx, fi.apexRelativePath(relPath), d.RelativeInstallPath).String()
 
 			copyCommands = append(copyCommands, "cp -f "+d.SrcPath.String()+" "+dataDest)
 			implicitInputs = append(implicitInputs, d.SrcPath)
 		}
 	}
-
-	// TODO(jiyong): use RuleBuilder
-	var emitCommands []string
-	imageContentFile := android.PathForModuleOut(ctx, "content.txt")
-	emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
-	minSdkVersion := a.minSdkVersion(ctx)
-	if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
-		emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
-	}
-	for _, fi := range a.filesInfo {
-		emitCommands = append(emitCommands, "echo './"+fi.path()+"' >> "+imageContentFile.String())
-	}
-	emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
 	implicitInputs = append(implicitInputs, a.manifestPbOut)
 
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// Step 1.a: Write the list of files in this APEX to a txt file and compare it against
+	// the allowed list given via the allowed_files property. Build fails when the two lists
+	// differ.
+	//
+	// TODO(jiyong): consider removing this. Nobody other than com.android.apex.cts.shim.* seems
+	// to be using this at this moment. Furthermore, this looks very similar to what
+	// buildInstalledFilesFile does. At least, move this to somewhere else so that this doesn't
+	// hurt readability.
+	// TODO(jiyong): use RuleBuilder
 	if a.overridableProperties.Allowed_files != nil {
+		// Build content.txt
+		var emitCommands []string
+		imageContentFile := android.PathForModuleOut(ctx, "content.txt")
+		emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
+		minSdkVersion := a.minSdkVersion(ctx)
+		if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
+			emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
+		}
+		for _, fi := range a.filesInfo {
+			emitCommands = append(emitCommands, "echo './"+fi.path()+"' >> "+imageContentFile.String())
+		}
+		emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        emitApexContentRule,
 			Implicits:   implicitInputs,
@@ -460,8 +486,9 @@
 			},
 		})
 		implicitInputs = append(implicitInputs, imageContentFile)
-		allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
 
+		// Compare content.txt against allowed_files.
+		allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
 		phonyOutput := android.PathForModuleOut(ctx, a.Name()+"-diff-phony-output")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        diffApexContentRule,
@@ -474,16 +501,19 @@
 				"apex_module_name":   a.Name(),
 			},
 		})
-
 		implicitInputs = append(implicitInputs, phonyOutput)
 	}
 
+	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
 	outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String()
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
 
-	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
 	if apexType == imageApex {
-		// files and dirs that will be created in APEX
+		////////////////////////////////////////////////////////////////////////////////////
+		// Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
+		// in this APEX. The file will be used by apexer in later steps.
+		// TODO(jiyong): make this as a function
+		// TODO(jiyong): use the RuleBuilder
 		var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
 		var executablePaths []string // this also includes dirs
 		var extractedAppSetPaths android.Paths
@@ -528,11 +558,17 @@
 				"apk_paths":  strings.Join(extractedAppSetDirs, " "),
 			},
 		})
+		implicitInputs = append(implicitInputs, cannedFsConfig)
 
+		////////////////////////////////////////////////////////////////////////////////////
+		// Step 3: Prepare option flags for apexer and invoke it to create an unsigned APEX.
+		// TODO(jiyong): use the RuleBuilder
 		optFlags := []string{}
 
-		// Additional implicit inputs.
-		implicitInputs = append(implicitInputs, cannedFsConfig, a.fileContexts, a.private_key_file, a.public_key_file)
+		fileContexts := a.buildFileContexts(ctx)
+		implicitInputs = append(implicitInputs, fileContexts)
+
+		implicitInputs = append(implicitInputs, a.private_key_file, a.public_key_file)
 		optFlags = append(optFlags, "--pubkey "+a.public_key_file.String())
 
 		manifestPackageName := a.getOverrideManifestPackageName(ctx)
@@ -546,15 +582,18 @@
 			optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String())
 		}
 
+		// Determine target/min sdk version from the context
+		// TODO(jiyong): make this as a function
 		moduleMinSdkVersion := a.minSdkVersion(ctx)
 		minSdkVersion := moduleMinSdkVersion.String()
 
-		// bundletool doesn't understand what "current" is. We need to transform it to codename
+		// bundletool doesn't understand what "current" is. We need to transform it to
+		// codename
 		if moduleMinSdkVersion.IsCurrent() {
 			minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
 		}
-		// apex module doesn't have a concept of target_sdk_version, hence for the time being
-		// targetSdkVersion == default targetSdkVersion of the branch.
+		// apex module doesn't have a concept of target_sdk_version, hence for the time
+		// being targetSdkVersion == default targetSdkVersion of the branch.
 		targetSdkVersion := strconv.Itoa(ctx.Config().DefaultAppTargetSdk(ctx).FinalOrFutureInt())
 
 		if java.UseApiFingerprint(ctx) {
@@ -595,8 +634,8 @@
 		}
 
 		if a.properties.Apex_name != nil {
-			// If apex_name is set, apexer can skip checking if key name matches with apex name.
-			// Note that apex_manifest is also mended.
+			// If apex_name is set, apexer can skip checking if key name matches with
+			// apex name.  Note that apex_manifest is also mended.
 			optFlags = append(optFlags, "--do_not_check_keyname")
 		}
 
@@ -617,13 +656,14 @@
 				"image_dir":        imageDir.String(),
 				"copy_commands":    strings.Join(copyCommands, " && "),
 				"manifest":         a.manifestPbOut.String(),
-				"file_contexts":    a.fileContexts.String(),
+				"file_contexts":    fileContexts.String(),
 				"canned_fs_config": cannedFsConfig.String(),
 				"key":              a.private_key_file.String(),
 				"opt_flags":        strings.Join(optFlags, " "),
 			},
 		})
 
+		// TODO(jiyong): make the two rules below as separate functions
 		apexProtoFile := android.PathForModuleOut(ctx, a.Name()+".pb"+suffix)
 		bundleModuleFile := android.PathForModuleOut(ctx, a.Name()+suffix+"-base.zip")
 		a.bundleModuleFile = bundleModuleFile
@@ -637,6 +677,15 @@
 
 		bundleConfig := a.buildBundleConfig(ctx)
 
+		var abis []string
+		for _, target := range ctx.MultiTargets() {
+			if len(target.Arch.Abi) > 0 {
+				abis = append(abis, target.Arch.Abi[0])
+			}
+		}
+
+		abis = android.FirstUniqueStrings(abis)
+
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        apexBundleRule,
 			Input:       apexProtoFile,
@@ -648,7 +697,7 @@
 				"config": bundleConfig.String(),
 			},
 		})
-	} else {
+	} else { // zipApex
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        zipApexRule,
 			Implicits:   implicitInputs,
@@ -663,16 +712,17 @@
 		})
 	}
 
+	////////////////////////////////////////////////////////////////////////////////////
+	// Step 4: Sign the APEX using signapk
 	a.outputFile = android.PathForModuleOut(ctx, a.Name()+suffix)
+
+	pem, key := a.getCertificateAndPrivateKey(ctx)
 	rule := java.Signapk
 	args := map[string]string{
-		"certificates": a.container_certificate_file.String() + " " + a.container_private_key_file.String(),
+		"certificates": pem.String() + " " + key.String(),
 		"flags":        "-a 4096", //alignment
 	}
-	implicits := android.Paths{
-		a.container_certificate_file,
-		a.container_private_key_file,
-	}
+	implicits := android.Paths{pem, key}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
 		rule = java.SignapkRE
 		args["implicits"] = strings.Join(implicits.Strings(), ",")
@@ -691,7 +741,6 @@
 	if a.installable() {
 		ctx.InstallFile(a.installDir, a.Name()+suffix, a.outputFile)
 	}
-	a.buildFilesInfo(ctx)
 
 	// installed-files.txt is dist'ed
 	a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir)
@@ -706,58 +755,50 @@
 	return true
 }
 
+// buildFlattenedApex creates rules for a flattened APEX. Flattened APEX actually doesn't have a
+// single output file. It is a phony target for all the files under /system/apex/<name> directory.
+// This function creates the installation rules for the files.
 func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) {
-	// Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it
-	// reply true to `InstallBypassMake()` (thus making the call
-	// `android.PathForModuleInstall` below use `android.pathForInstallInMakeDir`
-	// instead of `android.PathForOutput`) to return the correct path to the flattened
-	// APEX (as its contents is installed by Make, not Soong).
-	factx := flattenedApexContext{ctx}
-	a.outputFile = android.PathForModuleInstall(&factx, "apex", a.Name())
-	a.buildFilesInfo(ctx)
-}
-
-func (a *apexBundle) setCertificateAndPrivateKey(ctx android.ModuleContext) {
-	if a.container_certificate_file == nil {
-		cert := String(a.properties.Certificate)
-		if cert == "" {
-			pem, key := ctx.Config().DefaultAppCertificate(ctx)
-			a.container_certificate_file = pem
-			a.container_private_key_file = key
-		} else {
-			defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
-			a.container_certificate_file = defaultDir.Join(ctx, cert+".x509.pem")
-			a.container_private_key_file = defaultDir.Join(ctx, cert+".pk8")
-		}
-	}
-}
-
-func (a *apexBundle) buildFilesInfo(ctx android.ModuleContext) {
+	bundleName := a.Name()
 	if a.installable() {
-		// For flattened APEX, do nothing but make sure that APEX manifest and apex_pubkey are also copied along
-		// with other ordinary files.
-		a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil))
-
-		// rename to apex_pubkey
-		copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
-		ctx.Build(pctx, android.BuildParams{
-			Rule:   android.Cp,
-			Input:  a.public_key_file,
-			Output: copiedPubkey,
-		})
-		a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil))
-
-		if a.properties.ApexType == flattenedApex {
-			apexBundleName := a.Name()
-			for _, fi := range a.filesInfo {
-				dir := filepath.Join("apex", apexBundleName, fi.installDir)
-				target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.stem(), fi.builtFile)
-				for _, sym := range fi.symlinks {
-					ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
-				}
+		for _, fi := range a.filesInfo {
+			dir := filepath.Join("apex", bundleName, fi.installDir)
+			target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.stem(), fi.builtFile)
+			for _, sym := range fi.symlinks {
+				ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
 			}
 		}
 	}
+
+	a.fileContexts = a.buildFileContexts(ctx)
+
+	// Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it reply true
+	// to `InstallBypassMake()` (thus making the call `android.PathForModuleInstall` below use
+	// `android.pathForInstallInMakeDir` instead of `android.PathForOutput`) to return the
+	// correct path to the flattened APEX (as its contents is installed by Make, not Soong).
+	// TODO(jiyong): Why do we need to set outputFile for flattened APEX? We don't seem to use
+	// it and it actually points to a path that can never be built. Remove this.
+	factx := flattenedApexContext{ctx}
+	a.outputFile = android.PathForModuleInstall(&factx, "apex", bundleName)
+}
+
+// getCertificateAndPrivateKey retrieves the cert and the private key that will be used to sign
+// the zip container of this APEX. See the description of the 'certificate' property for how
+// the cert and the private key are found.
+func (a *apexBundle) getCertificateAndPrivateKey(ctx android.PathContext) (pem, key android.Path) {
+	if a.container_certificate_file != nil {
+		return a.container_certificate_file, a.container_private_key_file
+	}
+
+	cert := String(a.properties.Certificate)
+	if cert == "" {
+		return ctx.Config().DefaultAppCertificate(ctx)
+	}
+
+	defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
+	pem = defaultDir.Join(ctx, cert+".x509.pem")
+	key = defaultDir.Join(ctx, cert+".pk8")
+	return pem, key
 }
 
 func (a *apexBundle) getOverrideManifestPackageName(ctx android.ModuleContext) string {
diff --git a/apex/key.go b/apex/key.go
index 43764da..d9e3c10 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -127,13 +127,14 @@
 	apexKeyMap := make(map[string]apexKeyEntry)
 	ctx.VisitAllModules(func(module android.Module) {
 		if m, ok := module.(*apexBundle); ok && m.Enabled() && m.installable() {
+			pem, key := m.getCertificateAndPrivateKey(ctx)
 			apexKeyMap[m.Name()] = apexKeyEntry{
 				name:                  m.Name() + ".apex",
 				presigned:             false,
 				public_key:            m.public_key_file.String(),
 				private_key:           m.private_key_file.String(),
-				container_certificate: m.container_certificate_file.String(),
-				container_private_key: m.container_private_key_file.String(),
+				container_certificate: pem.String(),
+				container_private_key: key.String(),
 				partition:             m.PartitionTag(ctx.DeviceConfig()),
 			}
 		}
diff --git a/cmd/soong_build/queryview_test.go b/cmd/soong_build/queryview_test.go
index 525802a..9471a91 100644
--- a/cmd/soong_build/queryview_test.go
+++ b/cmd/soong_build/queryview_test.go
@@ -53,6 +53,12 @@
 	android.ModuleBase
 }
 
+// OutputFiles is needed because some instances of this module use dist with a
+// tag property which requires the module implements OutputFileProducer.
+func (m *customModule) OutputFiles(tag string) (android.Paths, error) {
+	return android.PathsForTesting("path" + tag), nil
+}
+
 func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// nothing for now.
 }
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index b910a70..22f712c 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -71,10 +71,6 @@
 
 	// Nested class loader subcontexts for dependencies.
 	Subcontexts []*ClassLoaderContext
-
-	// If the library is a shared library. This affects which elements of class loader context are
-	// added as <uses-library> tags by the manifest_fixer (dependencies of shared libraries aren't).
-	IsSharedLibrary bool
 }
 
 // ClassLoaderContextMap is a map from SDK version to a class loader context.
@@ -85,7 +81,7 @@
 
 // Add class loader context for the given library to the map entry for the given SDK version.
 func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
-	shared bool, hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
+	hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
 
 	// If missing dependencies are allowed, the build shouldn't fail when a <uses-library> is
 	// not found. However, this is likely to result is disabling dexpreopt, as it won't be
@@ -132,20 +128,19 @@
 	}
 
 	clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
-		Name:            lib,
-		Host:            hostPath,
-		Device:          devicePath,
-		Subcontexts:     subcontexts,
-		IsSharedLibrary: shared,
+		Name:        lib,
+		Host:        hostPath,
+		Device:      devicePath,
+		Subcontexts: subcontexts,
 	})
 	return nil
 }
 
 // Wrapper around addContext that reports errors.
 func (clcMap ClassLoaderContextMap) addContextOrReportError(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
-	shared bool, hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) {
+	hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) {
 
-	err := clcMap.addContext(ctx, sdkVer, lib, shared, hostPath, installPath, strict, nestedClcMap)
+	err := clcMap.addContext(ctx, sdkVer, lib, hostPath, installPath, strict, nestedClcMap)
 	if err != nil {
 		ctx.ModuleErrorf(err.Error())
 	}
@@ -153,25 +148,25 @@
 
 // Add class loader context. Fail on unknown build/install paths.
 func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, lib string,
-	shared bool, hostPath, installPath android.Path) {
+	hostPath, installPath android.Path) {
 
-	clcMap.addContextOrReportError(ctx, AnySdkVersion, lib, shared, hostPath, installPath, true, nil)
+	clcMap.addContextOrReportError(ctx, AnySdkVersion, lib, hostPath, installPath, true, nil)
 }
 
 // Add class loader context if the library exists. Don't fail on unknown build/install paths.
 func (clcMap ClassLoaderContextMap) MaybeAddContext(ctx android.ModuleInstallPathContext, lib *string,
-	shared bool, hostPath, installPath android.Path) {
+	hostPath, installPath android.Path) {
 
 	if lib != nil {
-		clcMap.addContextOrReportError(ctx, AnySdkVersion, *lib, shared, hostPath, installPath, false, nil)
+		clcMap.addContextOrReportError(ctx, AnySdkVersion, *lib, hostPath, installPath, false, nil)
 	}
 }
 
 // Add class loader context for the given SDK version. Fail on unknown build/install paths.
 func (clcMap ClassLoaderContextMap) AddContextForSdk(ctx android.ModuleInstallPathContext, sdkVer int,
-	lib string, shared bool, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
+	lib string, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
 
-	clcMap.addContextOrReportError(ctx, sdkVer, lib, shared, hostPath, installPath, true, nestedClcMap)
+	clcMap.addContextOrReportError(ctx, sdkVer, lib, hostPath, installPath, true, nestedClcMap)
 }
 
 // Merge the other class loader context map into this one, do not override existing entries.
@@ -208,26 +203,15 @@
 	}
 }
 
-// List of libraries in the unconditional class loader context, excluding dependencies of shared
-// libraries. These libraries should be in the <uses-library> tags in the manifest. Some of them may
-// be present in the original manifest, others are added by the manifest_fixer.
+// Returns top-level libraries in the CLC (conditional CLC, i.e. compatibility libraries are not
+// included). This is the list of libraries that should be in the <uses-library> tags in the
+// manifest. Some of them may be present in the source manifest, others are added by manifest_fixer.
 func (clcMap ClassLoaderContextMap) UsesLibs() (ulibs []string) {
 	if clcMap != nil {
-		// compatibility libraries (those in conditional context) are not added to <uses-library> tags
-		ulibs = usesLibsRec(clcMap[AnySdkVersion])
-		ulibs = android.FirstUniqueStrings(ulibs)
-	}
-	return ulibs
-}
-
-func usesLibsRec(clcs []*ClassLoaderContext) (ulibs []string) {
-	for _, clc := range clcs {
-		ulibs = append(ulibs, clc.Name)
-		// <uses-library> tags in the manifest should not include dependencies of shared libraries,
-		// because PackageManager already tracks all such dependencies and automatically adds their
-		// class loader contexts as subcontext of the shared library.
-		if !clc.IsSharedLibrary {
-			ulibs = append(ulibs, usesLibsRec(clc.Subcontexts)...)
+		clcs := clcMap[AnySdkVersion]
+		ulibs = make([]string, 0, len(clcs))
+		for _, clc := range clcs {
+			ulibs = append(ulibs, clc.Name)
 		}
 	}
 	return ulibs
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index abfca27..df68563 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -25,11 +25,6 @@
 	"android/soong/android"
 )
 
-const (
-	shared    = true  // dependencies are not added to uses libs
-	nonshared = false // dependencies are added to uses libs
-)
-
 func TestCLC(t *testing.T) {
 	// Construct class loader context with the following structure:
 	// .
@@ -55,36 +50,36 @@
 
 	m := make(ClassLoaderContextMap)
 
-	m.AddContext(ctx, "a", nonshared, buildPath(ctx, "a"), installPath(ctx, "a"))
-	m.AddContext(ctx, "b", shared, buildPath(ctx, "b"), installPath(ctx, "b"))
+	m.AddContext(ctx, "a", buildPath(ctx, "a"), installPath(ctx, "a"))
+	m.AddContext(ctx, "b", buildPath(ctx, "b"), installPath(ctx, "b"))
 
 	// "Maybe" variant in the good case: add as usual.
 	c := "c"
-	m.MaybeAddContext(ctx, &c, nonshared, buildPath(ctx, "c"), installPath(ctx, "c"))
+	m.MaybeAddContext(ctx, &c, buildPath(ctx, "c"), installPath(ctx, "c"))
 
 	// "Maybe" variant in the bad case: don't add library with unknown name, keep going.
-	m.MaybeAddContext(ctx, nil, nonshared, nil, nil)
+	m.MaybeAddContext(ctx, nil, nil, nil)
 
 	// Add some libraries with nested subcontexts.
 
 	m1 := make(ClassLoaderContextMap)
-	m1.AddContext(ctx, "a1", nonshared, buildPath(ctx, "a1"), installPath(ctx, "a1"))
-	m1.AddContext(ctx, "b1", shared, buildPath(ctx, "b1"), installPath(ctx, "b1"))
+	m1.AddContext(ctx, "a1", buildPath(ctx, "a1"), installPath(ctx, "a1"))
+	m1.AddContext(ctx, "b1", buildPath(ctx, "b1"), installPath(ctx, "b1"))
 
 	m2 := make(ClassLoaderContextMap)
-	m2.AddContext(ctx, "a2", nonshared, buildPath(ctx, "a2"), installPath(ctx, "a2"))
-	m2.AddContext(ctx, "b2", shared, buildPath(ctx, "b2"), installPath(ctx, "b2"))
-	m2.AddContextForSdk(ctx, AnySdkVersion, "c2", shared, buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
+	m2.AddContext(ctx, "a2", buildPath(ctx, "a2"), installPath(ctx, "a2"))
+	m2.AddContext(ctx, "b2", buildPath(ctx, "b2"), installPath(ctx, "b2"))
+	m2.AddContextForSdk(ctx, AnySdkVersion, "c2", buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
 
 	m3 := make(ClassLoaderContextMap)
-	m3.AddContext(ctx, "a3", nonshared, buildPath(ctx, "a3"), installPath(ctx, "a3"))
-	m3.AddContext(ctx, "b3", shared, buildPath(ctx, "b3"), installPath(ctx, "b3"))
+	m3.AddContext(ctx, "a3", buildPath(ctx, "a3"), installPath(ctx, "a3"))
+	m3.AddContext(ctx, "b3", buildPath(ctx, "b3"), installPath(ctx, "b3"))
 
-	m.AddContextForSdk(ctx, AnySdkVersion, "d", nonshared, buildPath(ctx, "d"), installPath(ctx, "d"), m2)
+	m.AddContextForSdk(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), m2)
 	// When the same library is both in conditional and unconditional context, it should be removed
 	// from conditional context.
-	m.AddContextForSdk(ctx, 42, "f", nonshared, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
-	m.AddContextForSdk(ctx, AnySdkVersion, "f", nonshared, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+	m.AddContextForSdk(ctx, 42, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+	m.AddContextForSdk(ctx, AnySdkVersion, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
 
 	// Merge map with implicit root library that is among toplevel contexts => does nothing.
 	m.AddContextMap(m1, "c")
@@ -93,12 +88,12 @@
 	m.AddContextMap(m3, "m_g")
 
 	// Compatibility libraries with unknown install paths get default paths.
-	m.AddContextForSdk(ctx, 29, AndroidHidlManager, nonshared, buildPath(ctx, AndroidHidlManager), nil, nil)
-	m.AddContextForSdk(ctx, 29, AndroidHidlBase, nonshared, buildPath(ctx, AndroidHidlBase), nil, nil)
+	m.AddContextForSdk(ctx, 29, AndroidHidlManager, buildPath(ctx, AndroidHidlManager), nil, nil)
+	m.AddContextForSdk(ctx, 29, AndroidHidlBase, buildPath(ctx, AndroidHidlBase), nil, nil)
 
 	// Add "android.test.mock" to conditional CLC, observe that is gets removed because it is only
 	// needed as a compatibility library if "android.test.runner" is in CLC as well.
-	m.AddContextForSdk(ctx, 30, AndroidTestMock, nonshared, buildPath(ctx, AndroidTestMock), nil, nil)
+	m.AddContextForSdk(ctx, 30, AndroidTestMock, buildPath(ctx, AndroidTestMock), nil, nil)
 
 	valid, validationError := validateClassLoaderContext(m)
 
@@ -158,7 +153,7 @@
 
 	// Test for libraries that are added by the manifest_fixer.
 	t.Run("uses libs", func(t *testing.T) {
-		wantUsesLibs := []string{"a", "b", "c", "d", "a2", "b2", "c2", "f", "a3", "b3"}
+		wantUsesLibs := []string{"a", "b", "c", "d", "f", "a3", "b3"}
 		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
 			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
 		}
@@ -169,7 +164,7 @@
 func TestCLCUnknownBuildPath(t *testing.T) {
 	ctx := testContext()
 	m := make(ClassLoaderContextMap)
-	err := m.addContext(ctx, AnySdkVersion, "a", nonshared, nil, nil, true, nil)
+	err := m.addContext(ctx, AnySdkVersion, "a", nil, nil, true, nil)
 	checkError(t, err, "unknown build path to <uses-library> \"a\"")
 }
 
@@ -177,7 +172,7 @@
 func TestCLCUnknownInstallPath(t *testing.T) {
 	ctx := testContext()
 	m := make(ClassLoaderContextMap)
-	err := m.addContext(ctx, AnySdkVersion, "a", nonshared, buildPath(ctx, "a"), nil, true, nil)
+	err := m.addContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), nil, true, nil)
 	checkError(t, err, "unknown install path to <uses-library> \"a\"")
 }
 
@@ -186,7 +181,7 @@
 
 	m := make(ClassLoaderContextMap)
 	a := "a"
-	m.MaybeAddContext(ctx, &a, nonshared, nil, nil)
+	m.MaybeAddContext(ctx, &a, nil, nil)
 
 	// The library should be added to <uses-library> tags by the manifest_fixer.
 	t.Run("maybe add", func(t *testing.T) {
@@ -208,9 +203,9 @@
 func TestCLCNestedConditional(t *testing.T) {
 	ctx := testContext()
 	m1 := make(ClassLoaderContextMap)
-	m1.AddContextForSdk(ctx, 42, "a", nonshared, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m1.AddContextForSdk(ctx, 42, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
 	m := make(ClassLoaderContextMap)
-	err := m.addContext(ctx, AnySdkVersion, "b", nonshared, buildPath(ctx, "b"), installPath(ctx, "b"), true, m1)
+	err := m.addContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), true, m1)
 	checkError(t, err, "nested class loader context shouldn't have conditional part")
 }
 
diff --git a/java/aar.go b/java/aar.go
index 799e763..1940d7f 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -426,7 +426,7 @@
 			// (including the java_sdk_library) itself then append any implicit sdk library
 			// names to the list of sdk libraries to be added to the manifest.
 			if component, ok := module.(SdkLibraryComponentDependency); ok {
-				classLoaderContexts.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(), true,
+				classLoaderContexts.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(),
 					component.DexJarBuildPath(), component.DexJarInstallPath())
 			}
 
diff --git a/java/androidmk.go b/java/androidmk.go
index 386a97f..25369ad 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -85,7 +85,6 @@
 	} else {
 		entriesList = append(entriesList, android.AndroidMkEntries{
 			Class:      "JAVA_LIBRARIES",
-			DistFiles:  library.distFiles,
 			OutputFile: android.OptionalPathForPath(library.outputFile),
 			Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 075b7aa..233e9d5 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -16,7 +16,6 @@
 
 import (
 	"reflect"
-	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -135,182 +134,6 @@
 	}
 }
 
-func TestDistWithTag(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo_without_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["hi"],
-			},
-		}
-		java_library {
-			name: "foo_with_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["hi"],
-				tag: ".jar",
-			},
-		}
-	`)
-
-	withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_without_tag", "android_common").Module())
-	withTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_with_tag", "android_common").Module())
-
-	if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
-		t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
-	}
-	if len(withTagEntries[0].DistFiles[".jar"]) != 1 ||
-		!strings.Contains(withTagEntries[0].DistFiles[".jar"][0].String(), "/javac/foo_with_tag.jar") {
-		t.Errorf("expected DistFiles to contain classes.jar, got %v", withTagEntries[0].DistFiles)
-	}
-	if len(withoutTagEntries[0].DistFiles[".jar"]) > 0 {
-		t.Errorf("did not expect explicit DistFile for .jar tag, got %v", withoutTagEntries[0].DistFiles[".jar"])
-	}
-}
-
-func TestDistWithDest(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["my_goal"],
-				dest: "my/custom/dest/dir",
-			},
-		}
-	`)
-
-	module := ctx.ModuleForTests("foo", "android_common").Module()
-	entries := android.AndroidMkEntriesForTest(t, config, "", module)
-	if len(entries) != 2 {
-		t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
-	}
-
-	distStrings := entries[0].GetDistForGoals(module)
-
-	if len(distStrings) != 2 {
-		t.Errorf("Expected 2 entries for dist: PHONY and dist-for-goals, but got %q", distStrings)
-	}
-
-	if distStrings[0] != ".PHONY: my_goal\n" {
-		t.Errorf("Expected .PHONY entry to declare my_goal, but got: %s", distStrings[0])
-	}
-
-	if !strings.Contains(distStrings[1], "$(call dist-for-goals,my_goal") ||
-		!strings.Contains(distStrings[1], ".intermediates/foo/android_common/dex/foo.jar:my/custom/dest/dir") {
-		t.Errorf(
-			"Expected dist-for-goals entry to contain my_goal and new dest dir, but got: %s", distStrings[1])
-	}
-}
-
-func TestDistsWithAllProperties(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["baz"],
-			},
-			dists: [
-				{
-					targets: ["bar"],
-					tag: ".jar",
-					dest: "bar.jar",
-					dir: "bar/dir",
-					suffix: ".qux",
-				},
-			]
-		}
-	`)
-
-	module := ctx.ModuleForTests("foo", "android_common").Module()
-	entries := android.AndroidMkEntriesForTest(t, config, "", module)
-	if len(entries) != 2 {
-		t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
-	}
-
-	distStrings := entries[0].GetDistForGoals(module)
-
-	if len(distStrings) != 4 {
-		t.Errorf("Expected 4 entries for dist: PHONY and dist-for-goals, but got %d", len(distStrings))
-	}
-
-	if distStrings[0] != ".PHONY: bar\n" {
-		t.Errorf("Expected .PHONY entry to declare bar, but got: %s", distStrings[0])
-	}
-
-	if !strings.Contains(distStrings[1], "$(call dist-for-goals,bar") ||
-		!strings.Contains(
-			distStrings[1],
-			".intermediates/foo/android_common/javac/foo.jar:bar/dir/bar.qux.jar") {
-		t.Errorf(
-			"Expected dist-for-goals entry to contain bar and new dest dir, but got: %s", distStrings[1])
-	}
-
-	if distStrings[2] != ".PHONY: baz\n" {
-		t.Errorf("Expected .PHONY entry to declare baz, but got: %s", distStrings[2])
-	}
-
-	if !strings.Contains(distStrings[3], "$(call dist-for-goals,baz") ||
-		!strings.Contains(distStrings[3], ".intermediates/foo/android_common/dex/foo.jar:foo.jar") {
-		t.Errorf(
-			"Expected dist-for-goals entry to contain my_other_goal and new dest dir, but got: %s",
-			distStrings[3])
-	}
-}
-
-func TestDistsWithTag(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo_without_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dists: [
-				{
-					targets: ["hi"],
-				},
-			],
-		}
-		java_library {
-			name: "foo_with_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dists: [
-				{
-					targets: ["hi"],
-					tag: ".jar",
-				},
-			],
-		}
-	`)
-
-	moduleWithoutTag := ctx.ModuleForTests("foo_without_tag", "android_common").Module()
-	moduleWithTag := ctx.ModuleForTests("foo_with_tag", "android_common").Module()
-
-	withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithoutTag)
-	withTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithTag)
-
-	if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
-		t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
-	}
-
-	distFilesWithoutTag := withoutTagEntries[0].DistFiles
-	distFilesWithTag := withTagEntries[0].DistFiles
-
-	if len(distFilesWithTag[".jar"]) != 1 ||
-		!strings.Contains(distFilesWithTag[".jar"][0].String(), "/javac/foo_with_tag.jar") {
-		t.Errorf("expected foo_with_tag's .jar-tagged DistFiles to contain classes.jar, got %v", distFilesWithTag[".jar"])
-	}
-	if len(distFilesWithoutTag[".jar"]) > 0 {
-		t.Errorf("did not expect foo_without_tag's .jar-tagged DistFiles to contain files, but got %v", distFilesWithoutTag[".jar"])
-	}
-}
-
 func TestJavaSdkLibrary_RequireXmlPermissionFile(t *testing.T) {
 	ctx, config := testJava(t, `
 		java_sdk_library {
diff --git a/java/app.go b/java/app.go
index 3446739..3384bbd 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1981,7 +1981,7 @@
 			if tag, ok := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag); ok {
 				dep := ctx.OtherModuleName(m)
 				if lib, ok := m.(Dependency); ok {
-					clcMap.AddContextForSdk(ctx, tag.sdkVersion, dep, isSharedSdkLibrary(m),
+					clcMap.AddContextForSdk(ctx, tag.sdkVersion, dep,
 						lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ClassLoaderContexts())
 				} else if ctx.Config().AllowMissingDependencies() {
 					ctx.AddMissingDependencies([]string{dep})
@@ -1995,11 +1995,6 @@
 	return clcMap
 }
 
-func isSharedSdkLibrary(m android.Module) bool {
-	lib, ok := m.(SdkLibraryDependency)
-	return ok && lib.IsSharedLibrary()
-}
-
 // enforceUsesLibraries returns true of <uses-library> tags should be checked against uses_libs and optional_uses_libs
 // properties.  Defaults to true if either of uses_libs or optional_uses_libs is specified.  Will default to true
 // unconditionally in the future.
diff --git a/java/java.go b/java/java.go
index 5012279..8738e00 100644
--- a/java/java.go
+++ b/java/java.go
@@ -456,8 +456,6 @@
 	// list of the xref extraction files
 	kytheFiles android.Paths
 
-	distFiles android.TaggedDistFiles
-
 	// Collect the module directory for IDE info in java/jdeps.go.
 	modulePaths []string
 
@@ -486,6 +484,8 @@
 	switch tag {
 	case "":
 		return append(android.Paths{j.outputFile}, j.extraOutputFiles...), nil
+	case android.DefaultDistTag:
+		return android.Paths{j.outputFile}, nil
 	case ".jar":
 		return android.Paths{j.implementationAndResourcesJar}, nil
 	case ".proguard_map":
@@ -1051,7 +1051,7 @@
 			case libTag:
 				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
 				// names of sdk libs that are directly depended are exported
-				j.classLoaderContexts.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(), true,
+				j.classLoaderContexts.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(),
 					dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			case staticLibTag:
 				ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
@@ -2105,15 +2105,13 @@
 	// add the name of that java_sdk_library to the exported sdk libs to make sure
 	// that, if necessary, a <uses-library> element for that java_sdk_library is
 	// added to the Android manifest.
-	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(), true,
+	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
 		j.DexJarBuildPath(), j.DexJarInstallPath())
 
 	// A non-SDK library may provide a <uses-library> (the name may be different from the module name).
 	if lib := proptools.String(j.usesLibraryProperties.Provides_uses_lib); lib != "" {
-		j.classLoaderContexts.AddContext(ctx, lib, true, j.DexJarBuildPath(), j.DexJarInstallPath())
+		j.classLoaderContexts.AddContext(ctx, lib, j.DexJarBuildPath(), j.DexJarInstallPath())
 	}
-
-	j.distFiles = j.GenerateTaggedDistFiles(ctx)
 }
 
 func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -2793,8 +2791,7 @@
 			case libTag:
 				flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
 				// names of sdk libs that are directly depended are exported
-				j.classLoaderContexts.AddContext(ctx, otherName, dep.IsSharedLibrary(),
-					dep.DexJarBuildPath(), dep.DexJarInstallPath())
+				j.classLoaderContexts.AddContext(ctx, otherName, dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			}
 		}
 	})
@@ -2809,7 +2806,7 @@
 	// add the name of that java_sdk_library to the exported sdk libs to make sure
 	// that, if necessary, a <uses-library> element for that java_sdk_library is
 	// added to the Android manifest.
-	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(), true,
+	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
 		outputFile, installFile)
 
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 603c808..32bc077 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -688,11 +688,6 @@
 	return c.namingScheme.apiModuleName(apiScope, c.moduleBase.BaseModuleName())
 }
 
-// If the SDK library is a shared library.
-func (c *commonToSdkLibraryAndImport) IsSharedLibrary() bool {
-	return c.sharedLibrary()
-}
-
 // The component names for different outputs of the java_sdk_library.
 //
 // They are similar to the names used for the child modules it creates
@@ -938,9 +933,6 @@
 	// jars for the stubs. The latter should only be needed when generating JavaDoc as otherwise
 	// they are identical to the corresponding header jars.
 	SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths
-
-	// If the SDK library is a shared library.
-	IsSharedLibrary() bool
 }
 
 type SdkLibrary struct {
@@ -1289,11 +1281,7 @@
 			Include_dirs       []string
 			Local_include_dirs []string
 		}
-		Dist struct {
-			Targets []string
-			Dest    *string
-			Dir     *string
-		}
+		Dists []android.Dist
 	}{}
 
 	// The stubs source processing uses the same compile time classpath when extracting the
@@ -1393,11 +1381,23 @@
 		}
 	}
 
-	// Dist the api txt artifact for sdk builds.
 	if !Bool(module.sdkLibraryProperties.No_dist) {
-		props.Dist.Targets = []string{"sdk", "win_sdk"}
-		props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.txt", module.distStem()))
-		props.Dist.Dir = proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
+		// Dist the api txt and removed api txt artifacts for sdk builds.
+		distDir := proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
+		for _, p := range []struct {
+			tag     string
+			pattern string
+		}{
+			{tag: ".api.txt", pattern: "%s.txt"},
+			{tag: ".removed-api.txt", pattern: "%s-removed.txt"},
+		} {
+			props.Dists = append(props.Dists, android.Dist{
+				Targets: []string{"sdk", "win_sdk"},
+				Dir:     distDir,
+				Dest:    proptools.StringPtr(fmt.Sprintf(p.pattern, module.distStem())),
+				Tag:     proptools.StringPtr(p.tag),
+			})
+		}
 	}
 
 	mctx.CreateModule(DroidstubsFactory, &props)
diff --git a/rust/config/global.go b/rust/config/global.go
index 6e268a0..22d9567 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.47.0"
+	RustDefaultVersion = "1.48.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2018"
 	Stdlibs            = []string{