Merge "Minor cleanup of soong.go."
diff --git a/Android.bp b/Android.bp
index e7a5de2..866ed25 100644
--- a/Android.bp
+++ b/Android.bp
@@ -75,6 +75,10 @@
     product_available: true,
     recovery_available: true,
     native_bridge_supported: true,
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
 
     arch: {
         arm: {
diff --git a/android/androidmk.go b/android/androidmk.go
index 4adbb22..a670656 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -177,24 +177,96 @@
 	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 {
+// AddCompatibilityTestSuites adds the supplied test suites to the EntryMap, with special handling
+// for partial MTS test suites.
+func (a *AndroidMkEntries) AddCompatibilityTestSuites(suites ...string) {
+	// MTS supports a full test suite and partial per-module MTS test suites, with naming mts-${MODULE}.
+	// To reduce repetition, if we find a partial MTS test suite without an full MTS test suite,
+	// we add the full test suite to our list.
+	if PrefixInList(suites, "mts-") && !InList("mts", suites) {
+		suites = append(suites, "mts")
+	}
+	a.AddStrings("LOCAL_COMPATIBILITY_SUITE", suites...)
+}
+
+// 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 +276,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 +290,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 +333,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 bb03378..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,101 +180,29 @@
 	assertEqual([]string{"qux"}, m.data.Target_required)
 }
 
-func TestGetDistForGoals(t *testing.T) {
-	testCases := []struct {
-		bp                     string
-		expectedAndroidMkLines []string
-	}{
-		{
-			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",
-			},
-		},
-		{
-			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",
-			},
-		},
-		{
-			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",
-			},
-		},
-		{
-			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",
-			},
-		},
-		{
-			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",
 			},
 		},
-		{
-			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: {
@@ -228,64 +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 {
-		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()
-
-		_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-		FailIfErrored(t, errs)
-		_, errs = ctx.PrepareBuildActions(config)
-		FailIfErrored(t, errs)
-
-		module := ctx.ModuleForTests("foo", "").Module().(*customModule)
-		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)
-
-		if len(androidMkLines) != len(testCase.expectedAndroidMkLines) {
+	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 %d AndroidMk lines, got %d:\n%v",
-				len(testCase.expectedAndroidMkLines),
-				len(androidMkLines),
-				androidMkLines,
+				"Expected AndroidMk line to be '%s', got '%s'",
+				expectedLine,
+				line,
 			)
 		}
-		for idx, line := range androidMkLines {
-			expectedLine := testCase.expectedAndroidMkLines[idx]
-			if line != expectedLine {
-				t.Errorf(
-					"Expected AndroidMk line to be '%s', got '%s'",
-					line,
-					expectedLine,
-				)
+	}
+}
+
+func distCopyForTest(from, to string) distCopy {
+	return distCopy{PathForTesting(from), to}
+}
+
+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))
+			}
+			distContributions := entries[0].getDistContributions(module)
+
+			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/config.go b/android/config.go
index 04c9129..57ab70b 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1321,6 +1321,14 @@
 	return Bool(c.productVariables.EnforceProductPartitionInterface)
 }
 
+func (c *config) EnforceInterPartitionJavaSdkLibrary() bool {
+	return Bool(c.productVariables.EnforceInterPartitionJavaSdkLibrary)
+}
+
+func (c *config) InterPartitionJavaLibraryAllowList() []string {
+	return c.productVariables.InterPartitionJavaLibraryAllowList
+}
+
 func (c *config) InstallExtraFlattenedApexes() bool {
 	return Bool(c.productVariables.InstallExtraFlattenedApexes)
 }
diff --git a/android/csuite_config.go b/android/csuite_config.go
index 15c518a..bf24d98 100644
--- a/android/csuite_config.go
+++ b/android/csuite_config.go
@@ -14,11 +14,6 @@
 
 package android
 
-import (
-	"fmt"
-	"io"
-)
-
 func init() {
 	RegisterModuleType("csuite_config", CSuiteConfigFactory)
 }
@@ -38,22 +33,21 @@
 	me.OutputFilePath = PathForModuleOut(ctx, me.BaseModuleName()).OutputPath
 }
 
-func (me *CSuiteConfig) AndroidMk() AndroidMkData {
-	androidMkData := AndroidMkData{
+func (me *CSuiteConfig) AndroidMkEntries() []AndroidMkEntries {
+	androidMkEntries := AndroidMkEntries{
 		Class:      "FAKE",
 		Include:    "$(BUILD_SYSTEM)/suite_host_config.mk",
 		OutputFile: OptionalPathForPath(me.OutputFilePath),
 	}
-	androidMkData.Extra = []AndroidMkExtraFunc{
-		func(w io.Writer, outputFile Path) {
+	androidMkEntries.ExtraEntries = []AndroidMkExtraEntriesFunc{
+		func(entries *AndroidMkEntries) {
 			if me.properties.Test_config != nil {
-				fmt.Fprintf(w, "LOCAL_TEST_CONFIG := %s\n",
-					*me.properties.Test_config)
+				entries.SetString("LOCAL_TEST_CONFIG", *me.properties.Test_config)
 			}
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE := csuite")
+			entries.AddCompatibilityTestSuites("csuite")
 		},
 	}
-	return androidMkData
+	return []AndroidMkEntries{androidMkEntries}
 }
 
 func InitCSuiteConfigModule(me *CSuiteConfig) {
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/android/packaging.go b/android/packaging.go
index 512e4ba..3e42060 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -21,10 +21,10 @@
 	"github.com/google/blueprint"
 )
 
-// PackagingSpec abstracts a request to place a built artifact at a certain path in a package.
-// A package can be the traditional <partition>.img, but isn't limited to those. Other examples could
-// be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS running
-// on a VM), or a zip archive for some of the host tools.
+// PackagingSpec abstracts a request to place a built artifact at a certain path in a package. A
+// package can be the traditional <partition>.img, but isn't limited to those. Other examples could
+// be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS
+// running on a VM), or a zip archive for some of the host tools.
 type PackagingSpec struct {
 	// Path relative to the root of the package
 	relPathInPackage string
@@ -40,15 +40,25 @@
 	executable bool
 }
 
+// Get file name of installed package
+func (p *PackagingSpec) FileName() string {
+	if p.relPathInPackage != "" {
+		return filepath.Base(p.relPathInPackage)
+	}
+
+	return ""
+}
+
 type PackageModule interface {
 	Module
 	packagingBase() *PackagingBase
 
 	// AddDeps adds dependencies to the `deps` modules. This should be called in DepsMutator.
-	AddDeps(ctx BottomUpMutatorContext)
+	// When adding the dependencies, depTag is used as the tag.
+	AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag)
 
 	// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
-	// returns zip entries in it.  This is expected to be called in GenerateAndroidBuildActions,
+	// returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
 	// followed by a build rule that unzips it and creates the final output (img, zip, tar.gz,
 	// etc.) from the extracted files
 	CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) []string
@@ -59,9 +69,9 @@
 type PackagingBase struct {
 	properties PackagingProperties
 
-	// Allows this module to skip missing dependencies. In most cases, this
-	// is not required, but for rare cases like when there's a dependency
-	// to a module which exists in certain repo checkouts, this is needed.
+	// Allows this module to skip missing dependencies. In most cases, this is not required, but
+	// for rare cases like when there's a dependency to a module which exists in certain repo
+	// checkouts, this is needed.
 	IgnoreMissingDependencies bool
 }
 
@@ -82,10 +92,6 @@
 	Multilib packagingMultilibProperties `android:"arch_variant"`
 }
 
-type packagingDependencyTag struct{ blueprint.BaseDependencyTag }
-
-var depTag = packagingDependencyTag{}
-
 func InitPackageModule(p PackageModule) {
 	base := p.packagingBase()
 	p.AddProperties(&base.properties)
@@ -95,10 +101,10 @@
 	return p
 }
 
-// From deps and multilib.*.deps, select the dependencies that are for the given arch
-// deps is for the current archicture when this module is not configured for multi target.
-// When configured for multi target, deps is selected for each of the targets and is NOT
-// selected for the current architecture which would be Common.
+// From deps and multilib.*.deps, select the dependencies that are for the given arch deps is for
+// the current archicture when this module is not configured for multi target. When configured for
+// multi target, deps is selected for each of the targets and is NOT selected for the current
+// architecture which would be Common.
 func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []string {
 	var ret []string
 	if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 {
@@ -134,7 +140,7 @@
 }
 
 // See PackageModule.AddDeps
-func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext) {
+func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag) {
 	for _, t := range p.getSupportedTargets(ctx) {
 		for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) {
 			if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) {
@@ -147,15 +153,9 @@
 
 // See PackageModule.CopyDepsToZip
 func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) (entries []string) {
-	var supportedArches []string
-	for _, t := range p.getSupportedTargets(ctx) {
-		supportedArches = append(supportedArches, t.Arch.ArchType.String())
-	}
 	m := make(map[string]PackagingSpec)
 	ctx.WalkDeps(func(child Module, parent Module) bool {
-		// Don't track modules with unsupported arch
-		// TODO(jiyong): remove this when aosp/1501613 lands.
-		if !InList(child.Target().Arch.ArchType.String(), supportedArches) {
+		if !IsInstallDepNeeded(ctx.OtherModuleDependencyTag(child)) {
 			return false
 		}
 		for _, ps := range child.PackagingSpecs() {
diff --git a/android/packaging_test.go b/android/packaging_test.go
index 7710c7f..7269bfb 100644
--- a/android/packaging_test.go
+++ b/android/packaging_test.go
@@ -17,6 +17,8 @@
 import (
 	"reflect"
 	"testing"
+
+	"github.com/google/blueprint"
 )
 
 // Module to be packaged
@@ -27,6 +29,12 @@
 	}
 }
 
+// dep tag used in this test. All dependencies are considered as installable.
+type installDepTag struct {
+	blueprint.BaseDependencyTag
+	InstallAlwaysNeededDependencyTag
+}
+
 func componentTestModuleFactory() Module {
 	m := &componentTestModule{}
 	m.AddProperties(&m.props)
@@ -35,7 +43,7 @@
 }
 
 func (m *componentTestModule) DepsMutator(ctx BottomUpMutatorContext) {
-	ctx.AddDependency(ctx.Module(), nil, m.props.Deps...)
+	ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...)
 }
 
 func (m *componentTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
@@ -61,7 +69,7 @@
 }
 
 func (m *packageTestModule) DepsMutator(ctx BottomUpMutatorContext) {
-	m.AddDeps(ctx)
+	m.AddDeps(ctx, installDepTag{})
 }
 
 func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
diff --git a/android/paths.go b/android/paths.go
index a62c9e3..b7117a3 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -665,7 +665,7 @@
 	return Paths(ret)
 }
 
-// WritablePaths is a slice of WritablePaths, used for multiple outputs.
+// WritablePaths is a slice of WritablePath, used for multiple outputs.
 type WritablePaths []WritablePath
 
 // Strings returns the string forms of the writable paths.
diff --git a/android/util.go b/android/util.go
index 65c5f1b..8e4c0f4 100644
--- a/android/util.go
+++ b/android/util.go
@@ -29,56 +29,44 @@
 	return append([]string(nil), s...)
 }
 
+// JoinWithPrefix prepends the prefix to each string in the list and
+// returns them joined together with " " as separator.
 func JoinWithPrefix(strs []string, prefix string) string {
 	if len(strs) == 0 {
 		return ""
 	}
 
-	if len(strs) == 1 {
-		return prefix + strs[0]
+	var buf strings.Builder
+	buf.WriteString(prefix)
+	buf.WriteString(strs[0])
+	for i := 1; i < len(strs); i++ {
+		buf.WriteString(" ")
+		buf.WriteString(prefix)
+		buf.WriteString(strs[i])
 	}
-
-	n := len(" ") * (len(strs) - 1)
-	for _, s := range strs {
-		n += len(prefix) + len(s)
-	}
-
-	ret := make([]byte, 0, n)
-	for i, s := range strs {
-		if i != 0 {
-			ret = append(ret, ' ')
-		}
-		ret = append(ret, prefix...)
-		ret = append(ret, s...)
-	}
-	return string(ret)
+	return buf.String()
 }
 
+// JoinWithSuffix appends the suffix to each string in the list and
+// returns them joined together with given separator.
 func JoinWithSuffix(strs []string, suffix string, separator string) string {
 	if len(strs) == 0 {
 		return ""
 	}
 
-	if len(strs) == 1 {
-		return strs[0] + suffix
+	var buf strings.Builder
+	buf.WriteString(strs[0])
+	buf.WriteString(suffix)
+	for i := 1; i < len(strs); i++ {
+		buf.WriteString(separator)
+		buf.WriteString(strs[i])
+		buf.WriteString(suffix)
 	}
-
-	n := len(" ") * (len(strs) - 1)
-	for _, s := range strs {
-		n += len(suffix) + len(s)
-	}
-
-	ret := make([]byte, 0, n)
-	for i, s := range strs {
-		if i != 0 {
-			ret = append(ret, separator...)
-		}
-		ret = append(ret, s...)
-		ret = append(ret, suffix...)
-	}
-	return string(ret)
+	return buf.String()
 }
 
+// SortedIntKeys returns the keys of the given integer-keyed map in the ascending order
+// TODO(asmundak): once Go has generics, combine this with SortedStringKeys below.
 func SortedIntKeys(m interface{}) []int {
 	v := reflect.ValueOf(m)
 	if v.Kind() != reflect.Map {
@@ -93,6 +81,7 @@
 	return s
 }
 
+// SorterStringKeys returns the keys of the given string-keyed map in the ascending order
 func SortedStringKeys(m interface{}) []string {
 	v := reflect.ValueOf(m)
 	if v.Kind() != reflect.Map {
@@ -107,6 +96,7 @@
 	return s
 }
 
+// SortedStringMapValues returns the values of the string-values map in the ascending order
 func SortedStringMapValues(m interface{}) []string {
 	v := reflect.ValueOf(m)
 	if v.Kind() != reflect.Map {
@@ -121,6 +111,7 @@
 	return s
 }
 
+// IndexList returns the index of the first occurrence of the given string in the list or -1
 func IndexList(s string, list []string) int {
 	for i, l := range list {
 		if l == s {
@@ -131,6 +122,7 @@
 	return -1
 }
 
+// InList checks if the string belongs to the list
 func InList(s string, list []string) bool {
 	return IndexList(s, list) != -1
 }
@@ -176,7 +168,10 @@
 	return -1
 }
 
+// FilterList divides the string list into two lists: one with the strings belonging
+// to the given filter list, and the other with the remaining ones
 func FilterList(list []string, filter []string) (remainder []string, filtered []string) {
+	// InList is O(n). May be worth using more efficient lookup for longer lists.
 	for _, l := range list {
 		if InList(l, filter) {
 			filtered = append(filtered, l)
@@ -188,6 +183,8 @@
 	return
 }
 
+// RemoveListFromList removes the strings belonging to the filter list from the
+// given list and returns the result
 func RemoveListFromList(list []string, filter_out []string) (result []string) {
 	result = make([]string, 0, len(list))
 	for _, l := range list {
@@ -198,20 +195,18 @@
 	return
 }
 
+// RemoveFromList removes given string from the string list.
 func RemoveFromList(s string, list []string) (bool, []string) {
-	i := IndexList(s, list)
-	if i == -1 {
-		return false, list
-	}
-
-	result := make([]string, 0, len(list)-1)
-	result = append(result, list[:i]...)
-	for _, l := range list[i+1:] {
-		if l != s {
-			result = append(result, l)
+	result := make([]string, 0, len(list))
+	var removed bool
+	for _, item := range list {
+		if item != s {
+			result = append(result, item)
+		} else {
+			removed = true
 		}
 	}
-	return true, result
+	return removed, result
 }
 
 // FirstUniqueStrings returns all unique elements of a slice of strings, keeping the first copy of
@@ -317,11 +312,10 @@
 	return s[1], s[2], true
 }
 
+// GetNumericSdkVersion removes the first occurrence of system_ in a string,
+// which is assumed to be something like "system_1.2.3"
 func GetNumericSdkVersion(v string) string {
-	if strings.Contains(v, "system_") {
-		return strings.Replace(v, "system_", "", 1)
-	}
-	return v
+	return strings.Replace(v, "system_", "", 1)
 }
 
 // copied from build/kati/strutil.go
@@ -334,17 +328,17 @@
 		return str
 	}
 	in := str
-	trimed := str
+	trimmed := str
 	if ps[0] != "" {
-		trimed = strings.TrimPrefix(in, ps[0])
-		if trimed == in {
+		trimmed = strings.TrimPrefix(in, ps[0])
+		if trimmed == in {
 			return str
 		}
 	}
-	in = trimed
+	in = trimmed
 	if ps[1] != "" {
-		trimed = strings.TrimSuffix(in, ps[1])
-		if trimed == in {
+		trimmed = strings.TrimSuffix(in, ps[1])
+		if trimmed == in {
 			return str
 		}
 	}
@@ -353,7 +347,7 @@
 	if len(rs) != 2 {
 		return repl
 	}
-	return rs[0] + trimed + rs[1]
+	return rs[0] + trimmed + rs[1]
 }
 
 // copied from build/kati/strutil.go
@@ -424,13 +418,15 @@
 	return ret
 }
 
+// CheckDuplicate checks if there are duplicates in given string list.
+// If there are, it returns first such duplicate and true.
 func CheckDuplicate(values []string) (duplicate string, found bool) {
 	seen := make(map[string]string)
 	for _, v := range values {
 		if duplicate, found = seen[v]; found {
-			return
+			return duplicate, true
 		}
 		seen[v] = v
 	}
-	return
+	return "", false
 }
diff --git a/android/variable.go b/android/variable.go
index a9a9c87..0df5272 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -347,6 +347,9 @@
 
 	EnforceProductPartitionInterface *bool `json:",omitempty"`
 
+	EnforceInterPartitionJavaSdkLibrary *bool    `json:",omitempty"`
+	InterPartitionJavaLibraryAllowList  []string `json:",omitempty"`
+
 	InstallExtraFlattenedApexes *bool `json:",omitempty"`
 
 	BoardUsesRecoveryAsBoot *bool `json:",omitempty"`
diff --git a/apex/Android.bp b/apex/Android.bp
index 1a5f683..9e8c30d 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -9,6 +9,7 @@
         "soong-cc",
         "soong-java",
         "soong-python",
+        "soong-rust",
         "soong-sh",
     ],
     srcs: [
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 a645b06..f127757 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -32,6 +32,7 @@
 	prebuilt_etc "android/soong/etc"
 	"android/soong/java"
 	"android/soong/python"
+	"android/soong/rust"
 	"android/soong/sh"
 )
 
@@ -179,6 +180,9 @@
 	// List of JNI libraries that are embedded inside this APEX.
 	Jni_libs []string
 
+	// List of rust dyn libraries
+	Rust_dyn_libs []string
+
 	// List of native executables that are embedded inside this APEX.
 	Binaries []string
 
@@ -349,8 +353,6 @@
 	lintReports android.Paths
 
 	prebuiltFileToDelete string
-
-	distFiles android.TaggedDistFiles
 }
 
 // apexFileClass represents a type of file that can be included in APEX.
@@ -513,13 +515,15 @@
 func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext, nativeModules ApexNativeDependencies, target android.Target, imageVariation string) {
 	binVariations := target.Variations()
 	libVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
+	rustLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "rust_libraries", Variation: "dylib"})
 
 	if ctx.Device() {
 		binVariations = append(binVariations, blueprint.Variation{Mutator: "image", Variation: imageVariation})
 		libVariations = append(libVariations,
 			blueprint.Variation{Mutator: "image", Variation: imageVariation},
-			blueprint.Variation{Mutator: "version", Variation: ""}, // "" is the non-stub variant
-		)
+			blueprint.Variation{Mutator: "version", Variation: ""}) // "" is the non-stub variant
+		rustLibVariations = append(rustLibVariations,
+			blueprint.Variation{Mutator: "image", Variation: imageVariation})
 	}
 
 	// Use *FarVariation* to be able to depend on modules having conflicting variations with
@@ -529,6 +533,7 @@
 	ctx.AddFarVariationDependencies(binVariations, testTag, nativeModules.Tests...)
 	ctx.AddFarVariationDependencies(libVariations, jniLibTag, nativeModules.Jni_libs...)
 	ctx.AddFarVariationDependencies(libVariations, sharedLibTag, nativeModules.Native_shared_libs...)
+	ctx.AddFarVariationDependencies(rustLibVariations, sharedLibTag, nativeModules.Rust_dyn_libs...)
 }
 
 func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
@@ -1096,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)
@@ -1264,6 +1270,35 @@
 	return af
 }
 
+func apexFileForRustExecutable(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
+	dirInApex := "bin"
+	if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
+	}
+	fileToCopy := rustm.OutputFile().Path()
+	androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
+	af := newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeExecutable, rustm)
+	return af
+}
+
+func apexFileForRustLibrary(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
+	// Decide the APEX-local directory by the multilib of the library
+	// In the future, we may query this to the module.
+	var dirInApex string
+	switch rustm.Arch().ArchType.Multilib {
+	case "lib32":
+		dirInApex = "lib"
+	case "lib64":
+		dirInApex = "lib64"
+	}
+	if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
+	}
+	fileToCopy := rustm.OutputFile().Path()
+	androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
+	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, rustm)
+}
+
 func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.Module) apexFile {
 	dirInApex := "bin"
 	fileToCopy := py.HostToolPath().Path()
@@ -1501,8 +1536,11 @@
 					filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py))
 				} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
 					filesInfo = append(filesInfo, apexFileForGoBinary(ctx, depName, gb))
+				} else if rust, ok := child.(*rust.Module); ok {
+					filesInfo = append(filesInfo, apexFileForRustExecutable(ctx, rust))
+					return true // track transitive dependencies
 				} else {
-					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
+					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
 				}
 			case javaLibTag:
 				switch child.(type) {
@@ -1663,6 +1701,13 @@
 					if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
 						filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
 					}
+				} else if rust.IsDylibDepTag(depTag) {
+					if rustm, ok := child.(*rust.Module); ok && rustm.IsInstallableToApex() {
+						af := apexFileForRustLibrary(ctx, rustm)
+						af.transitiveDep = true
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
+					}
 				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
 					// nothing
 				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
@@ -1786,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 {
@@ -1801,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/apex_test.go b/apex/apex_test.go
index 33e5077..a94e3b4 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -32,6 +32,7 @@
 	"android/soong/dexpreopt"
 	prebuilt_etc "android/soong/etc"
 	"android/soong/java"
+	"android/soong/rust"
 	"android/soong/sh"
 )
 
@@ -136,6 +137,8 @@
 
 	bp = bp + cc.GatherRequiredDepsForTest(android.Android)
 
+	bp = bp + rust.GatherRequiredDepsForTest()
+
 	bp = bp + java.GatherRequiredDepsForTest()
 
 	fs := map[string][]byte{
@@ -185,6 +188,7 @@
 		"bar/baz":                                    nil,
 		"testdata/baz":                               nil,
 		"AppSet.apks":                                nil,
+		"foo.rs":                                     nil,
 	}
 
 	cc.GatherRequiredFilesForTest(fs)
@@ -241,6 +245,7 @@
 	ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer)
 
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
+	rust.RegisterRequiredBuildComponentsForTest(ctx)
 
 	ctx.RegisterModuleType("cc_test", cc.TestFactory)
 	ctx.RegisterModuleType("vndk_prebuilt_shared", cc.VndkPrebuiltSharedFactory)
@@ -349,10 +354,12 @@
 			manifest: ":myapex.manifest",
 			androidManifest: ":myapex.androidmanifest",
 			key: "myapex.key",
+			binaries: ["foo.rust"],
 			native_shared_libs: ["mylib"],
+			rust_dyn_libs: ["libfoo.dylib.rust"],
 			multilib: {
 				both: {
-					binaries: ["foo",],
+					binaries: ["foo"],
 				}
 			},
 			java_libs: [
@@ -415,6 +422,28 @@
 			apex_available: [ "myapex", "com.android.gki.*" ],
 		}
 
+		rust_binary {
+		        name: "foo.rust",
+			srcs: ["foo.rs"],
+			rlibs: ["libfoo.rlib.rust"],
+			dylibs: ["libfoo.dylib.rust"],
+			apex_available: ["myapex"],
+		}
+
+		rust_library_rlib {
+		        name: "libfoo.rlib.rust",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			apex_available: ["myapex"],
+		}
+
+		rust_library_dylib {
+		        name: "libfoo.dylib.rust",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			apex_available: ["myapex"],
+		}
+
 		apex {
 			name: "com.android.gki.fake",
 			binaries: ["foo"],
@@ -529,16 +558,20 @@
 	ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("myjar"), "android_common_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("myjar_dex"), "android_common_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("foo.rust"), "android_arm64_armv8-a_apex10000")
 
 	// Ensure that apex variant is created for the indirect dep
 	ensureListContains(t, ctx.ModuleVariantsForTests("mylib2"), "android_arm64_armv8-a_shared_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("myotherjar"), "android_common_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.rlib.rust"), "android_arm64_armv8-a_rlib_dylib-std_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.dylib.rust"), "android_arm64_armv8-a_dylib_apex10000")
 
 	// Ensure that both direct and indirect deps are copied into apex
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib2.so")
 	ensureContains(t, copyCmds, "image.apex/javalib/myjar_stem.jar")
 	ensureContains(t, copyCmds, "image.apex/javalib/myjar_dex.jar")
+	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.dylib.rust.dylib.so")
 	// .. but not for java libs
 	ensureNotContains(t, copyCmds, "image.apex/javalib/myotherjar.jar")
 	ensureNotContains(t, copyCmds, "image.apex/javalib/msharedjar.jar")
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/cc/Android.bp b/cc/Android.bp
index ff2cdf3..88104e2 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -72,6 +72,8 @@
         "vendor_public_library.go",
 
         "testing.go",
+
+        "stub_library.go",
     ],
     testSrcs: [
         "cc_test.go",
diff --git a/cc/androidmk.go b/cc/androidmk.go
index d32e4de..320e69b 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -334,8 +334,7 @@
 	entries.Class = "NATIVE_TESTS"
 	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		if len(benchmark.Properties.Test_suites) > 0 {
-			entries.SetString("LOCAL_COMPATIBILITY_SUITE",
-				strings.Join(benchmark.Properties.Test_suites, " "))
+			entries.AddCompatibilityTestSuites(benchmark.Properties.Test_suites...)
 		}
 		if benchmark.testConfig != nil {
 			entries.SetString("LOCAL_FULL_TEST_CONFIG", benchmark.testConfig.String())
@@ -360,8 +359,7 @@
 	}
 	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		if len(test.Properties.Test_suites) > 0 {
-			entries.SetString("LOCAL_COMPATIBILITY_SUITE",
-				strings.Join(test.Properties.Test_suites, " "))
+			entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
 		}
 		if test.testConfig != nil {
 			entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
diff --git a/cc/cc.go b/cc/cc.go
index 6deb1b4..1a7ccf2 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -584,7 +584,7 @@
 	genHeaderExportDepTag = dependencyTag{name: "gen header export"}
 	objDepTag             = dependencyTag{name: "obj"}
 	linkerFlagsDepTag     = dependencyTag{name: "linker flags file"}
-	dynamicLinkerDepTag   = dependencyTag{name: "dynamic linker"}
+	dynamicLinkerDepTag   = installDependencyTag{name: "dynamic linker"}
 	reuseObjTag           = dependencyTag{name: "reuse objects"}
 	staticVariantTag      = dependencyTag{name: "static variant"}
 	vndkExtDepTag         = dependencyTag{name: "vndk extends"}
diff --git a/cc/proto.go b/cc/proto.go
index ae988ec..9c102a2 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -130,6 +130,8 @@
 			flags.protoC = true
 			flags.protoOptionsFile = true
 			flags.proto.OutTypeFlag = "--nanopb_out"
+			// Disable nanopb timestamps to support remote caching.
+			flags.proto.OutParams = append(flags.proto.OutParams, "-T")
 			plugin = "protoc-gen-nanopb"
 		case "full":
 			flags.proto.OutTypeFlag = "--cpp_out"
diff --git a/cc/stub_library.go b/cc/stub_library.go
new file mode 100644
index 0000000..76d236c
--- /dev/null
+++ b/cc/stub_library.go
@@ -0,0 +1,82 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+	"strings"
+
+	"android/soong/android"
+)
+
+func init() {
+	// Use singleton type to gather all generated soong modules.
+	android.RegisterSingletonType("stublibraries", stubLibrariesSingleton)
+}
+
+type stubLibraries struct {
+	stubLibraryMap map[string]bool
+}
+
+// Check if the module defines stub, or itself is stub
+func isStubTarget(m *Module) bool {
+	if m.IsStubs() || m.HasStubsVariants() {
+		return true
+	}
+
+	// Library which defines LLNDK Stub is also Stub target.
+	// Pure LLNDK Stub target would not contain any packaging
+	// with target file path.
+	if library, ok := m.linker.(*libraryDecorator); ok {
+		if library.Properties.Llndk_stubs != nil {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Get target file name to be installed from this module
+func getInstalledFileName(m *Module) string {
+	for _, ps := range m.PackagingSpecs() {
+		if name := ps.FileName(); name != "" {
+			return name
+		}
+	}
+	return ""
+}
+
+func (s *stubLibraries) GenerateBuildActions(ctx android.SingletonContext) {
+	// Visit all generated soong modules and store stub library file names.
+	ctx.VisitAllModules(func(module android.Module) {
+		if m, ok := module.(*Module); ok {
+			if isStubTarget(m) {
+				if name := getInstalledFileName(m); name != "" {
+					s.stubLibraryMap[name] = true
+				}
+			}
+		}
+	})
+}
+
+func stubLibrariesSingleton() android.Singleton {
+	return &stubLibraries{
+		stubLibraryMap: make(map[string]bool),
+	}
+}
+
+func (s *stubLibraries) MakeVars(ctx android.MakeVarsContext) {
+	// Convert stub library file names into Makefile variable.
+	ctx.Strict("STUB_LIBRARIES", strings.Join(android.SortedStringKeys(s.stubLibraryMap), " "))
+}
diff --git a/cc/testing.go b/cc/testing.go
index 198764b..95a93a0 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -166,6 +166,10 @@
 			product_available: true,
 			recovery_available: true,
 			src: "",
+			apex_available: [
+				"//apex_available:platform",
+				"//apex_available:anyapex",
+			],
 		}
 
 		toolchain_library {
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index db483f1..a4f57ea 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -16,6 +16,8 @@
 
 import (
 	"bytes"
+	"crypto/sha1"
+	"encoding/hex"
 	"errors"
 	"flag"
 	"fmt"
@@ -121,7 +123,22 @@
 		return fmt.Errorf("failed to create %q: %w", sandboxesRoot, err)
 	}
 
-	tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
+	// This tool assumes that there are no two concurrent runs with the same
+	// manifestFile. It should therefore be safe to use the hash of the
+	// manifestFile as the temporary directory name. We do this because it
+	// makes the temporary directory name deterministic. There are some
+	// tools that embed the name of the temporary output in the output, and
+	// they otherwise cause non-determinism, which then poisons actions
+	// depending on this one.
+	hash := sha1.New()
+	hash.Write([]byte(manifestFile))
+	tempDir := filepath.Join(sandboxesRoot, "sbox", hex.EncodeToString(hash.Sum(nil)))
+
+	err = os.RemoveAll(tempDir)
+	if err != nil {
+		return err
+	}
+	err = os.MkdirAll(tempDir, 0777)
 	if err != nil {
 		return fmt.Errorf("failed to create temporary dir in %q: %w", sandboxesRoot, err)
 	}
@@ -207,7 +224,7 @@
 	}
 
 	// Copy in any files specified by the manifest.
-	err = linkOrCopyFiles(command.CopyBefore, "", tempDir)
+	err = copyFiles(command.CopyBefore, "", tempDir)
 	if err != nil {
 		return "", err
 	}
@@ -315,12 +332,12 @@
 	return missingOutputErrors
 }
 
-// linkOrCopyFiles hardlinks or copies files in or out of the sandbox.
-func linkOrCopyFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+// copyFiles copies files in or out of the sandbox.
+func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
 	for _, copyPair := range copies {
 		fromPath := joinPath(fromDir, copyPair.GetFrom())
 		toPath := joinPath(toDir, copyPair.GetTo())
-		err := linkOrCopyOneFile(fromPath, toPath)
+		err := copyOneFile(fromPath, toPath)
 		if err != nil {
 			return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
 		}
@@ -328,30 +345,13 @@
 	return nil
 }
 
-// linkOrCopyOneFile first attempts to hardlink a file to a destination, and falls back to making
-// a copy if the hardlink fails.
-func linkOrCopyOneFile(from string, to string) error {
+// copyOneFile copies a file.
+func copyOneFile(from string, to string) error {
 	err := os.MkdirAll(filepath.Dir(to), 0777)
 	if err != nil {
 		return err
 	}
 
-	// First try hardlinking
-	err = os.Link(from, to)
-	if err != nil {
-		// Retry with copying in case the source an destination are on different filesystems.
-		// TODO: check for specific hardlink error?
-		err = copyOneFile(from, to)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// copyOneFile copies a file.
-func copyOneFile(from string, to string) error {
 	stat, err := os.Stat(from)
 	if err != nil {
 		return err
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..3759217 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -22,17 +22,214 @@
 	"android/soong/android"
 )
 
-// These libs are added as <uses-library> dependencies for apps if the targetSdkVersion in the
-// app manifest is less than the specified version. This is needed because these libraries haven't
-// existed prior to certain SDK version, but classes in them were in bootclasspath jars, etc.
-// Some of the compatibility libraries are optional (their <uses-library> tag has "required=false"),
-// so that if this library is missing this in not a build or run-time error.
+// This comment describes the following:
+//   1. the concept of class loader context (CLC) and its relation to classpath
+//   2. how PackageManager constructs CLC from shared libraries and their dependencies
+//   3. build-time vs. run-time CLC and why this matters for dexpreopt
+//   4. manifest fixer: a tool that adds missing <uses-library> tags to the manifests
+//   5. build system support for CLC
+//
+// 1. Class loader context
+// -----------------------
+//
+// Java libraries and apps that have run-time dependency on other libraries should list the used
+// libraries in their manifest (AndroidManifest.xml file). Each used library should be specified in
+// a <uses-library> tag that has the library name and an optional attribute specifying if the
+// library is optional or required. Required libraries are necessary for the library/app to run (it
+// will fail at runtime if the library cannot be loaded), and optional libraries are used only if
+// they are present (if not, the library/app can run without them).
+//
+// The libraries listed in <uses-library> tags are in the classpath of a library/app.
+//
+// Besides libraries, an app may also use another APK (for example in the case of split APKs), or
+// anything that gets added by the app dynamically. In general, it is impossible to know at build
+// time what the app may use at runtime. In the build system we focus on the known part: libraries.
+//
+// Class loader context (CLC) is a tree-like structure that describes class loader hierarchy. The
+// build system uses CLC in a more narrow sense: it is a tree of libraries that represents
+// transitive closure of all <uses-library> dependencies of a library/app. The top-level elements of
+// a CLC are the direct <uses-library> dependencies specified in the manifest (aka. classpath). Each
+// node of a CLC tree is a <uses-library> which may have its own <uses-library> sub-nodes.
+//
+// Because <uses-library> dependencies are, in general, a graph and not necessarily a tree, CLC may
+// contain subtrees for the same library multiple times. In other words, CLC is the dependency graph
+// "unfolded" to a tree. The duplication is only on a logical level, and the actual underlying class
+// loaders are not duplicated (at runtime there is a single class loader instance for each library).
+//
+// Example: A has <uses-library> tags B, C and D; C has <uses-library tags> B and D;
+//          D has <uses-library> E; B and E have no <uses-library> dependencies. The CLC is:
+//    A
+//    ├── B
+//    ├── C
+//    │   ├── B
+//    │   └── D
+//    │       └── E
+//    └── D
+//        └── E
+//
+// CLC defines the lookup order of libraries when resolving Java classes used by the library/app.
+// The lookup order is important because libraries may contain duplicate classes, and the class is
+// resolved to the first match.
+//
+// 2. PackageManager and "shared" libraries
+// ----------------------------------------
+//
+// In order to load an APK at runtime, PackageManager (in frameworks/base) creates a CLC. It adds
+// the libraries listed in the <uses-library> tags in the app's manifest as top-level CLC elements.
+// For each of the used libraries PackageManager gets all its <uses-library> dependencies (specified
+// as tags in the manifest of that library) and adds a nested CLC for each dependency. This process
+// continues recursively until all leaf nodes of the constructed CLC tree are libraries that have no
+// <uses-library> dependencies.
+//
+// PackageManager is aware only of "shared" libraries. The definition of "shared" here differs from
+// its usual meaning (as in shared vs. static). In Android, Java "shared" libraries are those listed
+// in /system/etc/permissions/platform.xml file. This file is installed on device. Each entry in it
+// contains the name of a "shared" library, a path to its DEX jar file and a list of dependencies
+// (other "shared" libraries that this one uses at runtime and specifies them in <uses-library> tags
+// in its manifest).
+//
+// In other words, there are two sources of information that allow PackageManager to construct CLC
+// at runtime: <uses-library> tags in the manifests and "shared" library dependencies in
+// /system/etc/permissions/platform.xml.
+//
+// 3. Build-time and run-time CLC and dexpreopt
+// --------------------------------------------
+//
+// CLC is needed not only when loading a library/app, but also when compiling it. Compilation may
+// happen either on device (known as "dexopt") or during the build (known as "dexpreopt"). Since
+// dexopt takes place on device, it has the same information as PackageManager (manifests and
+// shared library dependencies). Dexpreopt, on the other hand, takes place on host and in a totally
+// different environment, and it has to get the same information from the build system (see the
+// section about build system support below).
+//
+// Thus, the build-time CLC used by dexpreopt and the run-time CLC used by PackageManager are
+// the same thing, but computed in two different ways.
+//
+// It is important that build-time and run-time CLCs coincide, otherwise the AOT-compiled code
+// created by dexpreopt will be rejected. In order to check the equality of build-time and
+// run-time CLCs, the dex2oat compiler records build-time CLC in the *.odex files (in the
+// "classpath" field of the OAT file header). To find the stored CLC, use the following command:
+// `oatdump --oat-file=<FILE> | grep '^classpath = '`.
+//
+// Mismatch between build-time and run-time CLC is reported in logcat during boot (search with
+// `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'`. Mismatch is bad for performance, as it
+// forces the library/app to either be dexopted, or to run without any optimizations (e.g. the app's
+// code may need to be extracted in memory from the APK, a very expensive operation).
+//
+// A <uses-library> can be either optional or required. From dexpreopt standpoint, required library
+// must be present at build time (its absence is a build error). An optional library may be either
+// present or absent at build time: if present, it will be added to the CLC, passed to dex2oat and
+// recorded in the *.odex file; otherwise, if the library is absent, it will be skipped and not
+// added to CLC. If there is a mismatch between built-time and run-time status (optional library is
+// present in one case, but not the other), then the build-time and run-time CLCs won't match and
+// the compiled code will be rejected. It is unknown at build time if the library will be present at
+// runtime, therefore either including or excluding it may cause CLC mismatch.
+//
+// 4. Manifest fixer
+// -----------------
+//
+// Sometimes <uses-library> tags are missing from the source manifest of a library/app. This may
+// happen for example if one of the transitive dependencies of the library/app starts using another
+// <uses-library>, and the library/app's manifest isn't updated to include it.
+//
+// Soong can compute some of the missing <uses-library> tags for a given library/app automatically
+// as SDK libraries in the transitive dependency closure of the library/app. The closure is needed
+// because a library/app may depend on a static library that may in turn depend on an SDK library,
+// (possibly transitively via another library).
+//
+// Not all <uses-library> tags can be computed in this way, because some of the <uses-library>
+// dependencies are not SDK libraries, or they are not reachable via transitive dependency closure.
+// But when possible, allowing Soong to calculate the manifest entries is less prone to errors and
+// simplifies maintenance. For example, consider a situation when many apps use some static library
+// that adds a new <uses-library> dependency -- all the apps will have to be updated. That is
+// difficult to maintain.
+//
+// Soong computes the libraries that need to be in the manifest as the top-level libraries in CLC.
+// These libraries are passed to the manifest_fixer.
+//
+// All libraries added to the manifest should be "shared" libraries, so that PackageManager can look
+// up their dependencies and reconstruct the nested subcontexts at runtime. There is no build check
+// to ensure this, it is an assumption.
+//
+// 5. Build system support
+// -----------------------
+//
+// In order to construct CLC for dexpreopt and manifest_fixer, the build system needs to know all
+// <uses-library> dependencies of the dexpreopted library/app (including transitive dependencies).
+// For each <uses-librarry> dependency it needs to know the following information:
+//
+//   - the real name of the <uses-library> (it may be different from the module name)
+//   - build-time (on host) and run-time (on device) paths to the DEX jar file of the library
+//   - whether this library is optional or required
+//   - all <uses-library> dependencies
+//
+// Since the build system doesn't have access to the manifest contents (it cannot read manifests at
+// the time of build rule generation), it is necessary to copy this information to the Android.bp
+// and Android.mk files. For blueprints, the relevant properties are `uses_libs` and
+// `optional_uses_libs`. For makefiles, relevant variables are `LOCAL_USES_LIBRARIES` and
+// `LOCAL_OPTIONAL_USES_LIBRARIES`. It is preferable to avoid specifying these properties explicilty
+// when they can be computed automatically by Soong (as the transitive closure of SDK library
+// dependencies).
+//
+// Some of the Java libraries that are used as <uses-library> are not SDK libraries (they are
+// defined as `java_library` rather than `java_sdk_library` in the Android.bp files). In order for
+// the build system to handle them automatically like SDK libraries, it is possible to set a
+// property `provides_uses_lib` or variable `LOCAL_PROVIDES_USES_LIBRARY` on the blueprint/makefile
+// module of such library. This property can also be used to specify real library name in cases
+// when it differs from the module name.
+//
+// Because the information from the manifests has to be duplicated in the Android.bp/Android.mk
+// files, there is a danger that it may get out of sync. To guard against that, the build system
+// generates a rule that checks the metadata in the build files against the contents of a manifest
+// (verify_uses_libraries). The manifest can be available as a source file, or as part of a prebuilt
+// APK. Note that reading the manifests at the Ninja stage of the build is fine, unlike the build
+// rule generation phase.
+//
+// ClassLoaderContext is a structure that represents CLC.
+//
+type ClassLoaderContext struct {
+	// The name of the library.
+	Name string
+
+	// On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
+	Host android.Path
+
+	// On-device install path (used in dex2oat argument --stored-class-loader-context).
+	Device string
+
+	// Nested sub-CLC for dependencies.
+	Subcontexts []*ClassLoaderContext
+}
+
+// ClassLoaderContextMap is a map from SDK version to CLC. There is a special entry with key
+// AnySdkVersion that stores unconditional CLC that is added regardless of the target SDK version.
+//
+// Conditional CLC is for compatibility libraries which didn't exist prior to a certain SDK version
+// (say, N), but classes in them were in the bootclasspath jars, etc., and in version N they have
+// been separated into a standalone <uses-library>. Compatibility libraries should only be in the
+// CLC if the library/app that uses them has `targetSdkVersion` less than N in the manifest.
+//
+// Currently only apps (but not libraries) use conditional CLC.
+//
+// Target SDK version information is unavailable to the build system at rule generation time, so
+// the build system doesn't know whether conditional CLC is needed for a given app or not. So it
+// generates a build rule that includes conditional CLC for all versions, extracts the target SDK
+// version from the manifest, and filters the CLCs based on that version. Exact final CLC that is
+// passed to dex2oat is unknown to the build system, and gets known only at Ninja stage.
+//
+type ClassLoaderContextMap map[int][]*ClassLoaderContext
+
+// Compatibility libraries. Some are optional, and some are required: this is the default that
+// affects how they are handled by the Soong logic that automatically adds implicit SDK libraries
+// to the manifest_fixer, but an explicit `uses_libs`/`optional_uses_libs` can override this.
 var OrgApacheHttpLegacy = "org.apache.http.legacy"
 var AndroidTestBase = "android.test.base"
 var AndroidTestMock = "android.test.mock"
 var AndroidHidlBase = "android.hidl.base-V1.0-java"
 var AndroidHidlManager = "android.hidl.manager-V1.0-java"
 
+// Compatibility libraries grouped by version/optionality (for convenience, to avoid repeating the
+// same lists in multiple places).
 var OptionalCompatUsesLibs28 = []string{
 	OrgApacheHttpLegacy,
 }
@@ -55,37 +252,9 @@
 // last). We use the converntional "current" SDK level (10000), but any big number would do as well.
 const AnySdkVersion int = android.FutureApiLevelInt
 
-// ClassLoaderContext is a tree of libraries used by the dexpreopted module with their dependencies.
-// The context is used by dex2oat to compile the module and recorded in the AOT-compiled files, so
-// that it can be checked agains the run-time class loader context on device. If there is a mismatch
-// at runtime, AOT-compiled code is rejected.
-type ClassLoaderContext struct {
-	// The name of the library (same as the name of the module that contains it).
-	Name string
-
-	// On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
-	Host android.Path
-
-	// On-device install path (used in dex2oat argument --stored-class-loader-context).
-	Device string
-
-	// 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.
-// There is a special entry with key AnySdkVersion that stores unconditional class loader context.
-// Other entries store conditional contexts that should be added for some apps that have
-// targetSdkVersion in the manifest lower than the key SDK version.
-type ClassLoaderContextMap map[int][]*ClassLoaderContext
-
 // 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 +301,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 +321,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 +376,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
@@ -273,6 +430,7 @@
 	return true, nil
 }
 
+// Helper function for validateClassLoaderContext() that handles recursion.
 func validateClassLoaderContextRec(sdkVer int, clcs []*ClassLoaderContext) (bool, error) {
 	for _, clc := range clcs {
 		if clc.Host == nil || clc.Device == UnknownInstallLibraryPath {
@@ -312,6 +470,7 @@
 	return clcStr, android.FirstUniquePaths(paths)
 }
 
+// Helper function for ComputeClassLoaderContext() that handles recursion.
 func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) {
 	var paths android.Paths
 	var clcsHost, clcsTarget []string
@@ -336,7 +495,7 @@
 	return clcHost, clcTarget, paths
 }
 
-// Paths to a <uses-library> on host and on device.
+// JSON representation of <uses-library> paths on host and on device.
 type jsonLibraryPath struct {
 	Host   string
 	Device string
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/filesystem/filesystem.go b/filesystem/filesystem.go
index a1605b4..ac33b76 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -18,6 +18,8 @@
 	"fmt"
 
 	"android/soong/android"
+
+	"github.com/google/blueprint"
 )
 
 func init() {
@@ -27,8 +29,16 @@
 type filesystem struct {
 	android.ModuleBase
 	android.PackagingBase
+
+	output     android.OutputPath
+	installDir android.InstallPath
 }
 
+// android_filesystem packages a set of modules and their transitive dependencies into a filesystem
+// image. The filesystem images are expected to be mounted in the target device, which means the
+// modules in the filesystem image are built for the target device (i.e. Android, not Linux host).
+// The modules are placed in the filesystem image just like they are installed to the ordinary
+// partitions like system.img. For example, cc_library modules are placed under ./lib[64] directory.
 func filesystemFactory() android.Module {
 	module := &filesystem{}
 	android.InitPackageModule(module)
@@ -36,8 +46,14 @@
 	return module
 }
 
+var dependencyTag = struct{ blueprint.BaseDependencyTag }{}
+
 func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) {
-	f.AddDeps(ctx)
+	f.AddDeps(ctx, dependencyTag)
+}
+
+func (f *filesystem) installFileName() string {
+	return f.BaseModuleName() + ".img"
 }
 
 var pctx = android.NewPackageContext("android/soong/filesystem")
@@ -64,13 +80,32 @@
 		Text(">").Output(propFile).
 		Implicit(mkuserimg)
 
-	image := android.PathForModuleOut(ctx, "filesystem.img").OutputPath
+	f.output = android.PathForModuleOut(ctx, "filesystem.img").OutputPath
 	builder.Command().BuiltTool(ctx, "build_image").
 		Text(rootDir.String()). // input directory
 		Input(propFile).
-		Output(image).
+		Output(f.output).
 		Text(rootDir.String()) // directory where to find fs_config_files|dirs
 
 	// rootDir is not deleted. Might be useful for quick inspection.
 	builder.Build(pctx, ctx, "build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
+
+	f.installDir = android.PathForModuleInstall(ctx, "etc")
+	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
+}
+
+var _ android.AndroidMkEntriesProvider = (*filesystem)(nil)
+
+// Implements android.AndroidMkEntriesProvider
+func (f *filesystem) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(f.output),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_PATH", f.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
+			},
+		},
+	}}
 }
diff --git a/genrule/genrule.go b/genrule/genrule.go
index cd7c52f..e5ee3fc 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// A genrule module takes a list of source files ("srcs" property), an optional
+// list of tools ("tools" property), and a command line ("cmd" property), to
+// generate output files ("out" property).
+
 package genrule
 
 import (
@@ -47,6 +51,8 @@
 var (
 	pctx = android.NewPackageContext("android/soong/genrule")
 
+	// Used by gensrcs when there is more than 1 shard to merge the outputs
+	// of each shard into a zip file.
 	gensrcsMerge = pctx.AndroidStaticRule("gensrcsMerge", blueprint.RuleParams{
 		Command:        "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}",
 		CommandDeps:    []string{"${soongZip}", "${zipSync}"},
@@ -115,6 +121,7 @@
 	// Properties for Bazel migration purposes.
 	bazel.Properties
 }
+
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
@@ -127,6 +134,9 @@
 
 	properties generatorProperties
 
+	// For the different tasks that genrule and gensrc generate. genrule will
+	// generate 1 task, and gensrc will generate 1 or more tasks based on the
+	// number of shards the input files are sharded into.
 	taskGenerator taskFunc
 
 	deps        android.Paths
@@ -148,14 +158,17 @@
 type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
 
 type generateTask struct {
-	in      android.Paths
-	out     android.WritablePaths
-	depFile android.WritablePath
-	copyTo  android.WritablePaths
-	genDir  android.WritablePath
-	cmd     string
-	shard   int
-	shards  int
+	in         android.Paths
+	out        android.WritablePaths
+	depFile    android.WritablePath
+	copyTo     android.WritablePaths // For gensrcs to set on gensrcsMerge rule.
+	genDir     android.WritablePath
+	extraTools android.Paths // dependencies on tools used by the generator
+
+	cmd string
+	// For gensrsc sharding.
+	shard  int
+	shards int
 }
 
 func (g *Module) GeneratedSourceFiles() android.Paths {
@@ -322,6 +335,7 @@
 	var outputFiles android.WritablePaths
 	var zipArgs strings.Builder
 
+	// Generate tasks, either from genrule or gensrcs.
 	for _, task := range g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) {
 		if len(task.out) == 0 {
 			ctx.ModuleErrorf("must have at least one output file")
@@ -434,6 +448,7 @@
 		cmd.ImplicitOutputs(task.out)
 		cmd.Implicits(task.in)
 		cmd.Implicits(g.deps)
+		cmd.Implicits(task.extraTools)
 		if Bool(g.properties.Depfile) {
 			cmd.ImplicitDepFile(task.depFile)
 		}
@@ -584,8 +599,8 @@
 		for i, shard := range shards {
 			var commands []string
 			var outFiles android.WritablePaths
+			var commandDepFiles []string
 			var copyTo android.WritablePaths
-			var depFile android.WritablePath
 
 			// When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each
 			// shard will be write to their own directories and then be merged together
@@ -598,7 +613,7 @@
 
 			genDir := android.PathForModuleGen(ctx, genSubDir)
 
-			for j, in := range shard {
+			for _, in := range shard {
 				outFile := android.GenPathWithExt(ctx, finalSubDir, in, String(properties.Output_extension))
 
 				// If sharding is enabled, then outFile is the path to the output file in
@@ -610,10 +625,6 @@
 					outFile = shardFile
 				}
 
-				if j == 0 {
-					depFile = outFile.ReplaceExtension(ctx, "d")
-				}
-
 				outFiles = append(outFiles, outFile)
 
 				// pre-expand the command line to replace $in and $out with references to
@@ -624,6 +635,12 @@
 						return in.String(), nil
 					case "out":
 						return android.SboxPathForOutput(outFile, genDir), nil
+					case "depfile":
+						// Generate a depfile for each output file.  Store the list for
+						// later in order to combine them all into a single depfile.
+						depFile := android.SboxPathForOutput(outFile.ReplaceExtension(ctx, "d"), genDir)
+						commandDepFiles = append(commandDepFiles, depFile)
+						return depFile, nil
 					default:
 						return "$(" + name + ")", nil
 					}
@@ -638,15 +655,29 @@
 			}
 			fullCommand := strings.Join(commands, " && ")
 
+			var outputDepfile android.WritablePath
+			var extraTools android.Paths
+			if len(commandDepFiles) > 0 {
+				// Each command wrote to a depfile, but ninja can only handle one
+				// depfile per rule.  Use the dep_fixer tool at the end of the
+				// command to combine all the depfiles into a single output depfile.
+				outputDepfile = android.PathForModuleGen(ctx, genSubDir, "gensrcs.d")
+				depFixerTool := ctx.Config().HostToolPath(ctx, "dep_fixer")
+				fullCommand += fmt.Sprintf(" && %s -o $(depfile) %s",
+					depFixerTool.String(), strings.Join(commandDepFiles, " "))
+				extraTools = append(extraTools, depFixerTool)
+			}
+
 			generateTasks = append(generateTasks, generateTask{
-				in:      shard,
-				out:     outFiles,
-				depFile: depFile,
-				copyTo:  copyTo,
-				genDir:  genDir,
-				cmd:     fullCommand,
-				shard:   i,
-				shards:  len(shards),
+				in:         shard,
+				out:        outFiles,
+				depFile:    outputDepfile,
+				copyTo:     copyTo,
+				genDir:     genDir,
+				cmd:        fullCommand,
+				shard:      i,
+				shards:     len(shards),
+				extraTools: extraTools,
 			})
 		}
 
diff --git a/java/Android.bp b/java/Android.bp
index 9e8dc78..39502b3 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -48,6 +48,7 @@
         "robolectric.go",
         "sdk.go",
         "sdk_library.go",
+        "sdk_library_external.go",
         "support_libraries.go",
         "sysprop.go",
         "system_modules.go",
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..fc573c8 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{
@@ -140,9 +139,9 @@
 func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string) {
 	entries.SetString("LOCAL_MODULE_TAGS", "tests")
 	if len(test_suites) > 0 {
-		entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", test_suites...)
+		entries.AddCompatibilityTestSuites(test_suites...)
 	} else {
-		entries.SetString("LOCAL_COMPATIBILITY_SUITE", "null-suite")
+		entries.AddCompatibilityTestSuites("null-suite")
 	}
 }
 
@@ -524,17 +523,12 @@
 	// Note that dstubs.apiFile can be also be nil if WITHOUT_CHECKS_API is true.
 	// TODO(b/146727827): Revert when we do not need to generate stubs and API separately.
 
-	var distFiles android.TaggedDistFiles
-	if dstubs.apiFile != nil {
-		distFiles = android.MakeDefaultDistFiles(dstubs.apiFile)
-	}
 	outputFile := android.OptionalPathForPath(dstubs.stubsSrcJar)
 	if !outputFile.Valid() {
 		outputFile = android.OptionalPathForPath(dstubs.apiFile)
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
-		DistFiles:  distFiles,
 		OutputFile: outputFile,
 		Include:    "$(BUILD_SYSTEM)/soong_droiddoc_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/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index f9975ba..5df5845 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -25,11 +25,177 @@
 	"github.com/google/blueprint/proptools"
 )
 
+// This comment describes:
+//   1. ART boot images in general (their types, structure, file layout, etc.)
+//   2. build system support for boot images
+//
+// 1. ART boot images
+// ------------------
+//
+// A boot image in ART is a set of files that contain AOT-compiled native code and a heap snapshot
+// of AOT-initialized classes for the bootclasspath Java libraries. A boot image is compiled from a
+// set of DEX jars by the dex2oat compiler. A boot image is used for two purposes: 1) it is
+// installed on device and loaded at runtime, and 2) other Java libraries and apps are compiled
+// against it (compilation may take place either on host, known as "dexpreopt", or on device, known
+// as "dexopt").
+//
+// A boot image is not a single file, but a collection of interrelated files. Each boot image has a
+// number of components that correspond to the Java libraries that constitute it. For each component
+// there are multiple files:
+//   - *.oat or *.odex file with native code (architecture-specific, one per instruction set)
+//   - *.art file with pre-initialized Java classes (architecture-specific, one per instruction set)
+//   - *.vdex file with verification metadata for the DEX bytecode (architecture independent)
+//
+// *.vdex files for the boot images do not contain the DEX bytecode itself, because the
+// bootclasspath DEX files are stored on disk in uncompressed and aligned form. Consequently a boot
+// image is not self-contained and cannot be used without its DEX files. To simplify the management
+// of boot image files, ART uses a certain naming scheme and associates the following metadata with
+// each boot image:
+//   - A stem, which is a symbolic name that is prepended to boot image file names.
+//   - A location (on-device path to the boot image files).
+//   - A list of boot image locations (on-device paths to dependency boot images).
+//   - A set of DEX locations (on-device paths to the DEX files, one location for one DEX file used
+//     to compile the boot image).
+//
+// There are two kinds of boot images:
+//   - primary boot images
+//   - boot image extensions
+//
+// 1.1. Primary boot images
+// ------------------------
+//
+// A primary boot image is compiled for a core subset of bootclasspath Java libraries. It does not
+// depend on any other images, and other boot images may depend on it.
+//
+// For example, assuming that the stem is "boot", the location is /apex/com.android.art/javalib/,
+// the set of core bootclasspath libraries is A B C, and the boot image is compiled for ARM targets
+// (32 and 64 bits), it will have three components with the following files:
+//   - /apex/com.android.art/javalib/{arm,arm64}/boot.{art,oat,vdex}
+//   - /apex/com.android.art/javalib/{arm,arm64}/boot-B.{art,oat,vdex}
+//   - /apex/com.android.art/javalib/{arm,arm64}/boot-C.{art,oat,vdex}
+//
+// The files of the first component are special: they do not have the component name appended after
+// the stem. This naming convention dates back to the times when the boot image was not split into
+// components, and there were just boot.oat and boot.art. The decision to split was motivated by
+// licensing reasons for one of the bootclasspath libraries.
+//
+// As of November 2020 the only primary boot image in Android is the image in the ART APEX
+// com.android.art. The primary ART boot image contains the Core libraries that are part of the ART
+// module. When the ART module gets updated, the primary boot image will be updated with it, and all
+// dependent images will get invalidated (the checksum of the primary image stored in dependent
+// images will not match), unless they are updated in sync with the ART module.
+//
+// 1.2. Boot image extensions
+// --------------------------
+//
+// A boot image extension is compiled for a subset of bootclasspath Java libraries (in particular,
+// this subset does not include the Core bootclasspath libraries that go into the primary boot
+// image). A boot image extension depends on the primary boot image and optionally some other boot
+// image extensions. Other images may depend on it. In other words, boot image extensions can form
+// acyclic dependency graphs.
+//
+// The motivation for boot image extensions comes from the Mainline project. Consider a situation
+// when the list of bootclasspath libraries is A B C, and both A and B are parts of the Android
+// platform, but C is part of an updatable APEX com.android.C. When the APEX is updated, the Java
+// code for C might have changed compared to the code that was used to compile the boot image.
+// Consequently, the whole boot image is obsolete and invalidated (even though the code for A and B
+// that does not depend on C is up to date). To avoid this, the original monolithic boot image is
+// split in two parts: the primary boot image that contains A B, and the boot image extension that
+// contains C and depends on the primary boot image (extends it).
+//
+// For example, assuming that the stem is "boot", the location is /system/framework, the set of
+// bootclasspath libraries is D E (where D is part of the platform and is located in
+// /system/framework, and E is part of a non-updatable APEX com.android.E and is located in
+// /apex/com.android.E/javalib), and the boot image is compiled for ARM targets (32 and 64 bits),
+// it will have two components with the following files:
+//   - /system/framework/{arm,arm64}/boot-D.{art,oat,vdex}
+//   - /system/framework/{arm,arm64}/boot-E.{art,oat,vdex}
+//
+// As of November 2020 the only boot image extension in Android is the Framework boot image
+// extension. It extends the primary ART boot image and contains Framework libraries and other
+// bootclasspath libraries from the platform and non-updatable APEXes that are not included in the
+// ART image. The Framework boot image extension is updated together with the platform. In the
+// future other boot image extensions may be added for some updatable modules.
+//
+//
+// 2. Build system support for boot images
+// ---------------------------------------
+//
+// The primary ART boot image needs to be compiled with one dex2oat invocation that depends on DEX
+// jars for the core libraries. Framework boot image extension needs to be compiled with one dex2oat
+// invocation that depends on the primary ART boot image and all bootclasspath DEX jars except the
+// Core libraries.
+//
+// 2.1. Libraries that go in the boot images
+// -----------------------------------------
+//
+// The contents of each boot image are determined by the PRODUCT variables. The primary ART APEX
+// boot image contains libraries listed in the ART_APEX_JARS variable in the AOSP makefiles. The
+// Framework boot image extension contains libraries specified in the PRODUCT_BOOT_JARS and
+// PRODUCT_BOOT_JARS_EXTRA variables. The AOSP makefiles specify some common Framework libraries,
+// but more product-specific libraries can be added in the product makefiles.
+//
+// Each component of the PRODUCT_BOOT_JARS and PRODUCT_BOOT_JARS_EXTRA variables is either a simple
+// name (if the library is a part of the Platform), or a colon-separated pair <apex, name> (if the
+// library is a part of a non-updatable APEX).
+//
+// A related variable PRODUCT_UPDATABLE_BOOT_JARS contains bootclasspath libraries that are in
+// updatable APEXes. They are not included in the boot image.
+//
+// One exception to the above rules are "coverage" builds (a special build flavor which requires
+// setting environment variable EMMA_INSTRUMENT_FRAMEWORK=true). In coverage builds the Java code in
+// boot image libraries is instrumented, which means that the instrumentation library (jacocoagent)
+// needs to be added to the list of bootclasspath DEX jars.
+//
+// In general, there is a requirement that the source code for a boot image library must be
+// available at build time (e.g. it cannot be a stub that has a separate implementation library).
+//
+// 2.2. Static configs
+// -------------------
+//
+// Because boot images are used to dexpreopt other Java modules, the paths to boot image files must
+// be known by the time dexpreopt build rules for the dependent modules are generated. Boot image
+// configs are constructed very early during the build, before build rule generation. The configs
+// provide predefined paths to boot image files (these paths depend only on static build
+// configuration, such as PRODUCT variables, and use hard-coded directory names).
+//
+// 2.3. Singleton
+// --------------
+//
+// Build rules for the boot images are generated with a Soong singleton. Because a singleton has no
+// dependencies on other modules, it has to find the modules for the DEX jars using VisitAllModules.
+// Soong loops through all modules and compares each module against a list of bootclasspath library
+// names. Then it generates build rules that copy DEX jars from their intermediate module-specific
+// locations to the hard-coded locations predefined in the boot image configs.
+//
+// It would be possible to use a module with proper dependencies instead, but that would require
+// changes in the way Soong generates variables for Make: a singleton can use one MakeVars() method
+// that writes variables to out/soong/make_vars-*.mk, which is included early by the main makefile,
+// but module(s) would have to use out/soong/Android-*.mk which has a group of LOCAL_* variables
+// for each module, and is included later.
+//
+// 2.4. Install rules
+// ------------------
+//
+// The primary boot image and the Framework extension are installed in different ways. The primary
+// boot image is part of the ART APEX: it is copied into the APEX intermediate files, packaged
+// together with other APEX contents, extracted and mounted on device. The Framework boot image
+// extension is installed by the rules defined in makefiles (make/core/dex_preopt_libart.mk). Soong
+// writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names,
+// paths and so on.
+//
+// 2.5. JIT-Zygote configuration
+// -----------------------------
+//
+// One special configuration is JIT-Zygote build, when the primary ART image is used for compiling
+// apps instead of the Framework boot image extension (see DEXPREOPT_USE_ART_IMAGE and UseArtImage).
+//
+
 func init() {
 	RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext)
 }
 
-// Target-independent description of pre-compiled boot image.
+// Target-independent description of a boot image.
 type bootImageConfig struct {
 	// If this image is an extension, the image that it extends.
 	extends *bootImageConfig
@@ -66,7 +232,7 @@
 	variants []*bootImageVariant
 }
 
-// Target-dependent description of pre-compiled boot image.
+// Target-dependent description of a boot image.
 type bootImageVariant struct {
 	*bootImageConfig
 
@@ -90,6 +256,7 @@
 	unstrippedInstalls android.RuleBuilderInstalls
 }
 
+// Get target-specific boot image variant for the given boot image config and target.
 func (image bootImageConfig) getVariant(target android.Target) *bootImageVariant {
 	for _, variant := range image.variants {
 		if variant.target.Os == target.Os && variant.target.Arch.ArchType == target.Arch.ArchType {
@@ -99,7 +266,7 @@
 	return nil
 }
 
-// Return any (the first) variant which is for the device (as opposed to for the host)
+// Return any (the first) variant which is for the device (as opposed to for the host).
 func (image bootImageConfig) getAnyAndroidVariant() *bootImageVariant {
 	for _, variant := range image.variants {
 		if variant.target.Os == android.Android {
@@ -109,10 +276,12 @@
 	return nil
 }
 
+// Return the name of a boot image module given a boot image config and a component (module) index.
+// A module name is a combination of the Java library name, and the boot image stem (that is stored
+// in the config).
 func (image bootImageConfig) moduleName(ctx android.PathContext, idx int) string {
-	// Dexpreopt on the boot class path produces multiple files. The first dex file
-	// is converted into 'name'.art (to match the legacy assumption that 'name'.art
-	// exists), and the rest are converted to 'name'-<jar>.art.
+	// The first module of the primary boot image is special: its module name has only the stem, but
+	// not the library name. All other module names are of the form <stem>-<library name>
 	m := image.modules.Jar(idx)
 	name := image.stem
 	if idx != 0 || image.extends != nil {
@@ -121,6 +290,7 @@
 	return name
 }
 
+// Return the name of the first boot image module, or stem if the list of modules is empty.
 func (image bootImageConfig) firstModuleNameOrStem(ctx android.PathContext) string {
 	if image.modules.Len() > 0 {
 		return image.moduleName(ctx, 0)
@@ -129,6 +299,8 @@
 	}
 }
 
+// Return filenames for the given boot image component, given the output directory and a list of
+// extensions.
 func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) android.OutputPaths {
 	ret := make(android.OutputPaths, 0, image.modules.Len()*len(exts))
 	for i := 0; i < image.modules.Len(); i++ {
@@ -140,17 +312,26 @@
 	return ret
 }
 
+// Return boot image locations (as a list of symbolic paths).
+//
 // The image "location" is a symbolic path that, with multiarchitecture support, doesn't really
 // exist on the device. Typically it is /apex/com.android.art/javalib/boot.art and should be the
 // same for all supported architectures on the device. The concrete architecture specific files
 // actually end up in architecture-specific sub-directory such as arm, arm64, x86, or x86_64.
 //
-// For example a physical file
-// "/apex/com.android.art/javalib/x86/boot.art" has "image location"
-// "/apex/com.android.art/javalib/boot.art" (which is not an actual file).
+// For example a physical file /apex/com.android.art/javalib/x86/boot.art has "image location"
+// /apex/com.android.art/javalib/boot.art (which is not an actual file).
+//
+// For a primary boot image the list of locations has a single element.
+//
+// For a boot image extension the list of locations contains a location for all dependency images
+// (including the primary image) and the location of the extension itself. For example, for the
+// Framework boot image extension that depends on the primary ART boot image the list contains two
+// elements.
 //
 // The location is passed as an argument to the ART tools like dex2oat instead of the real path.
 // ART tools will then reconstruct the architecture-specific real path.
+//
 func (image *bootImageVariant) imageLocations() (imageLocations []string) {
 	if image.extends != nil {
 		imageLocations = image.extends.getVariant(image.target).imageLocations()
@@ -158,18 +339,6 @@
 	return append(imageLocations, dexpreopt.PathToLocation(image.images, image.target.Arch.ArchType))
 }
 
-func concat(lists ...[]string) []string {
-	var size int
-	for _, l := range lists {
-		size += len(l)
-	}
-	ret := make([]string, 0, size)
-	for _, l := range lists {
-		ret = append(ret, l...)
-	}
-	return ret
-}
-
 func dexpreoptBootJarsFactory() android.Singleton {
 	return &dexpreoptBootJars{}
 }
@@ -182,10 +351,21 @@
 	return dexpreopt.GetGlobalConfig(ctx).DisablePreopt
 }
 
+// Singleton for generating boot image build rules.
 type dexpreoptBootJars struct {
+	// Default boot image config (currently always the Framework boot image extension). It should be
+	// noted that JIT-Zygote builds use ART APEX image instead of the Framework boot image extension,
+	// but the switch is handled not here, but in the makefiles (triggered with
+	// DEXPREOPT_USE_ART_IMAGE=true).
 	defaultBootImage *bootImageConfig
-	otherImages      []*bootImageConfig
 
+	// Other boot image configs (currently the list contains only the primary ART APEX image. It
+	// used to contain an experimental JIT-Zygote image (now replaced with the ART APEX image). In
+	// the future other boot image extensions may be added.
+	otherImages []*bootImageConfig
+
+	// Build path to a config file that Soong writes for Make (to be used in makefiles that install
+	// the default boot image).
 	dexpreoptConfigForMake android.WritablePath
 }
 
@@ -205,7 +385,7 @@
 	return files
 }
 
-// dexpreoptBoot singleton rules
+// Generate build rules for boot images.
 func (d *dexpreoptBootJars) GenerateBuildActions(ctx android.SingletonContext) {
 	if skipDexpreoptBootJars(ctx) {
 		return
@@ -334,9 +514,10 @@
 		}
 	}
 
-	// The path to bootclasspath dex files needs to be known at module GenerateAndroidBuildAction time, before
-	// the bootclasspath modules have been compiled.  Copy the dex jars there so the module rules that have
-	// already been set up can find them.
+	// The paths to bootclasspath DEX files need to be known at module GenerateAndroidBuildAction
+	// time, before the boot images are built (these paths are used in dexpreopt rule generation for
+	// Java libraries and apps). Generate rules that copy bootclasspath DEX jars to the predefined
+	// paths.
 	for i := range bootDexJars {
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   android.Cp,
@@ -371,6 +552,7 @@
 	return image
 }
 
+// Generate boot image build rules for a specific target.
 func buildBootImageVariant(ctx android.SingletonContext, image *bootImageVariant,
 	profile android.Path, missingDeps []string) android.WritablePaths {
 
@@ -428,12 +610,15 @@
 	}
 
 	if image.extends != nil {
+		// It is a boot image extension, so it needs the boot image it depends on (in this case the
+		// primary ART APEX image).
 		artImage := image.primaryImages
 		cmd.
 			Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
 			Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":").
 			FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage)
 	} else {
+		// It is a primary image, so it needs a base address.
 		cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress())
 	}
 
@@ -717,7 +902,9 @@
 	android.WriteFileRule(ctx, path, string(data))
 }
 
-// Export paths for default boot image to Make
+// Define Make variables for boot image names, paths, etc. These variables are used in makefiles
+// (make/core/dex_preopt_libart.mk) to generate install rules that copy boot image files to the
+// correct output directories.
 func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) {
 	if d.dexpreoptConfigForMake != nil {
 		ctx.Strict("DEX_PREOPT_CONFIG_FOR_MAKE", d.dexpreoptConfigForMake.String())
@@ -731,6 +918,11 @@
 		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.getAnyAndroidVariant().dexLocationsDeps, " "))
 
 		var imageNames []string
+		// TODO: the primary ART boot image should not be exposed to Make, as it is installed in a
+		// different way as a part of the ART APEX. However, there is a special JIT-Zygote build
+		// configuration which uses the primary ART image instead of the Framework boot image
+		// extension, and it relies on the ART image being exposed to Make. To fix this, it is
+		// necessary to rework the logic in makefiles.
 		for _, current := range append(d.otherImages, image) {
 			imageNames = append(imageNames, current.name)
 			for _, variant := range current.variants {
diff --git a/java/droiddoc.go b/java/droiddoc.go
index cf7f4fb..da8489a 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -1050,7 +1050,8 @@
 		return android.Paths{d.stubsSrcJar}, nil
 	case ".docs.zip":
 		return android.Paths{d.docZip}, nil
-	case ".api.txt":
+	case ".api.txt", android.DefaultDistTag:
+		// This is the default dist path for dist properties that have no tag property.
 		return android.Paths{d.apiFilePath}, nil
 	case ".removed-api.txt":
 		return android.Paths{d.removedApiFilePath}, nil
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index e57f323..8f0e09c 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -259,7 +259,7 @@
 		FlagWithInput("--blocked ",
 			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-force-blocked.txt")).
 		FlagWithInput("--blocked ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-temp-blocklist.txt")).
+			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-temp-blocklist.txt")).FlagWithArg("--tag ", "lo-prio").
 		FlagWithInput("--unsupported ", android.PathForSource(
 			ctx, "frameworks/base/config/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
 		FlagWithOutput("--output ", tempPath)
diff --git a/java/java.go b/java/java.go
index 5012279..9000f74 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":
@@ -772,6 +772,37 @@
 	libDeps := ctx.AddVariationDependencies(nil, libTag, rewriteSyspropLibs(j.properties.Libs, "libs")...)
 	ctx.AddVariationDependencies(nil, staticLibTag, rewriteSyspropLibs(j.properties.Static_libs, "static_libs")...)
 
+	if ctx.DeviceConfig().VndkVersion() != "" && ctx.Config().EnforceInterPartitionJavaSdkLibrary() {
+		// Require java_sdk_library at inter-partition java dependency to ensure stable
+		// interface between partitions. If inter-partition java_library dependency is detected,
+		// raise build error because java_library doesn't have a stable interface.
+		//
+		// Inputs:
+		//    PRODUCT_ENFORCE_INTER_PARTITION_JAVA_SDK_LIBRARY
+		//      if true, enable enforcement
+		//    PRODUCT_INTER_PARTITION_JAVA_LIBRARY_ALLOWLIST
+		//      exception list of java_library names to allow inter-partition dependency
+		for idx, lib := range j.properties.Libs {
+			if libDeps[idx] == nil {
+				continue
+			}
+
+			if _, ok := syspropPublicStubs[lib]; ok {
+				continue
+			}
+
+			if javaDep, ok := libDeps[idx].(javaSdkLibraryEnforceContext); ok {
+				// java_sdk_library is always allowed at inter-partition dependency.
+				// So, skip check.
+				if _, ok := javaDep.(*SdkLibrary); ok {
+					continue
+				}
+
+				j.checkPartitionsForJavaDependency(ctx, "libs", javaDep)
+			}
+		}
+	}
+
 	// For library dependencies that are component libraries (like stubs), add the implementation
 	// as a dependency (dexpreopt needs to be against the implementation library, not stubs).
 	for _, dep := range libDeps {
@@ -1051,7 +1082,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 +2136,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 +2822,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 +2837,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/java_test.go b/java/java_test.go
index 83db443..f7cf03f 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -805,6 +805,165 @@
 	})
 }
 
+func TestJavaSdkLibraryEnforce(t *testing.T) {
+	partitionToBpOption := func(partition string) string {
+		switch partition {
+		case "system":
+			return ""
+		case "vendor":
+			return "soc_specific: true,"
+		case "product":
+			return "product_specific: true,"
+		default:
+			panic("Invalid partition group name: " + partition)
+		}
+	}
+
+	type testConfigInfo struct {
+		libraryType                string
+		fromPartition              string
+		toPartition                string
+		enforceVendorInterface     bool
+		enforceProductInterface    bool
+		enforceJavaSdkLibraryCheck bool
+		allowList                  []string
+	}
+
+	createTestConfig := func(info testConfigInfo) android.Config {
+		bpFileTemplate := `
+			java_library {
+				name: "foo",
+				srcs: ["foo.java"],
+				libs: ["bar"],
+				sdk_version: "current",
+				%s
+			}
+
+			%s {
+				name: "bar",
+				srcs: ["bar.java"],
+				sdk_version: "current",
+				%s
+			}
+		`
+
+		bpFile := fmt.Sprintf(bpFileTemplate,
+			partitionToBpOption(info.fromPartition),
+			info.libraryType,
+			partitionToBpOption(info.toPartition))
+
+		config := testConfig(nil, bpFile, nil)
+		configVariables := config.TestProductVariables
+
+		configVariables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface)
+		if info.enforceVendorInterface {
+			configVariables.DeviceVndkVersion = proptools.StringPtr("current")
+		}
+		configVariables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck)
+		configVariables.InterPartitionJavaLibraryAllowList = info.allowList
+
+		return config
+	}
+
+	isValidDependency := func(configInfo testConfigInfo) bool {
+		if configInfo.enforceVendorInterface == false {
+			return true
+		}
+
+		if configInfo.enforceJavaSdkLibraryCheck == false {
+			return true
+		}
+
+		if inList("bar", configInfo.allowList) {
+			return true
+		}
+
+		if configInfo.libraryType == "java_library" {
+			if configInfo.fromPartition != configInfo.toPartition {
+				if !configInfo.enforceProductInterface &&
+					((configInfo.fromPartition == "system" && configInfo.toPartition == "product") ||
+						(configInfo.fromPartition == "product" && configInfo.toPartition == "system")) {
+					return true
+				}
+				return false
+			}
+		}
+
+		return true
+	}
+
+	errorMessage := "is not allowed across the partitions"
+
+	allPartitionCombinations := func() [][2]string {
+		var result [][2]string
+		partitions := []string{"system", "vendor", "product"}
+
+		for _, fromPartition := range partitions {
+			for _, toPartition := range partitions {
+				result = append(result, [2]string{fromPartition, toPartition})
+			}
+		}
+
+		return result
+	}
+
+	allFlagCombinations := func() [][3]bool {
+		var result [][3]bool
+		flagValues := [2]bool{false, true}
+
+		for _, vendorInterface := range flagValues {
+			for _, productInterface := range flagValues {
+				for _, enableEnforce := range flagValues {
+					result = append(result, [3]bool{vendorInterface, productInterface, enableEnforce})
+				}
+			}
+		}
+
+		return result
+	}
+
+	for _, libraryType := range []string{"java_library", "java_sdk_library"} {
+		for _, partitionValues := range allPartitionCombinations() {
+			for _, flagValues := range allFlagCombinations() {
+				testInfo := testConfigInfo{
+					libraryType:                libraryType,
+					fromPartition:              partitionValues[0],
+					toPartition:                partitionValues[1],
+					enforceVendorInterface:     flagValues[0],
+					enforceProductInterface:    flagValues[1],
+					enforceJavaSdkLibraryCheck: flagValues[2],
+				}
+
+				if isValidDependency(testInfo) {
+					testJavaWithConfig(t, createTestConfig(testInfo))
+				} else {
+					testJavaErrorWithConfig(t, errorMessage, createTestConfig(testInfo))
+				}
+			}
+		}
+	}
+
+	testJavaWithConfig(t, createTestConfig(testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+		allowList:                  []string{"bar"},
+	}))
+
+	testJavaErrorWithConfig(t, errorMessage, createTestConfig(testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+		allowList:                  []string{"foo"},
+	}))
+}
+
 func TestDefaults(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_defaults {
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/java/sdk_library_external.go b/java/sdk_library_external.go
new file mode 100644
index 0000000..2934936
--- /dev/null
+++ b/java/sdk_library_external.go
@@ -0,0 +1,109 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"android/soong/android"
+)
+
+type partitionGroup int
+
+// Representation of partition group for checking inter-partition library dependencies.
+// Between system and system_ext, there are no restrictions of dependencies,
+// so we can treat these partitions as the same in terms of inter-partition dependency.
+// Same policy is applied between vendor and odm partiton.
+const (
+	partitionGroupNone partitionGroup = iota
+	// group for system, and system_ext partition
+	partitionGroupSystem
+	// group for vendor and odm partition
+	partitionGroupVendor
+	// product partition
+	partitionGroupProduct
+)
+
+func (g partitionGroup) String() string {
+	switch g {
+	case partitionGroupSystem:
+		return "system"
+	case partitionGroupVendor:
+		return "vendor"
+	case partitionGroupProduct:
+		return "product"
+	}
+
+	return ""
+}
+
+// Get partition group of java module that can be used at inter-partition dependency check.
+// We currently have three groups
+//   (system, system_ext) => system partition group
+//   (vendor, odm) => vendor partition group
+//   (product) => product partition group
+func (j *Module) partitionGroup(ctx android.EarlyModuleContext) partitionGroup {
+	// system and system_ext partition can be treated as the same in terms of inter-partition dependency.
+	if j.Platform() || j.SystemExtSpecific() {
+		return partitionGroupSystem
+	}
+
+	// vendor and odm partition can be treated as the same in terms of inter-partition dependency.
+	if j.SocSpecific() || j.DeviceSpecific() {
+		return partitionGroupVendor
+	}
+
+	// product partition is independent.
+	if j.ProductSpecific() {
+		return partitionGroupProduct
+	}
+
+	panic("Cannot determine partition type")
+}
+
+func (j *Module) allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool {
+	return inList(j.Name(), ctx.Config().InterPartitionJavaLibraryAllowList())
+}
+
+type javaSdkLibraryEnforceContext interface {
+	Name() string
+	allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool
+	partitionGroup(ctx android.EarlyModuleContext) partitionGroup
+}
+
+var _ javaSdkLibraryEnforceContext = (*Module)(nil)
+
+func (j *Module) checkPartitionsForJavaDependency(ctx android.EarlyModuleContext, propName string, dep javaSdkLibraryEnforceContext) {
+	if dep.allowListedInterPartitionJavaLibrary(ctx) {
+		return
+	}
+
+	// If product interface is not enforced, skip check between system and product partition.
+	// But still need to check between product and vendor partition because product interface flag
+	// just represents enforcement between product and system, and vendor interface enforcement
+	// that is enforced here by precondition is representing enforcement between vendor and other partitions.
+	if !ctx.Config().EnforceProductPartitionInterface() {
+		productToSystem := j.partitionGroup(ctx) == partitionGroupProduct && dep.partitionGroup(ctx) == partitionGroupSystem
+		systemToProduct := j.partitionGroup(ctx) == partitionGroupSystem && dep.partitionGroup(ctx) == partitionGroupProduct
+
+		if productToSystem || systemToProduct {
+			return
+		}
+	}
+
+	// If module and dependency library is inter-partition
+	if j.partitionGroup(ctx) != dep.partitionGroup(ctx) {
+		errorFormat := "dependency on java_library (%q) is not allowed across the partitions (%s -> %s), use java_sdk_library instead"
+		ctx.PropertyErrorf(propName, errorFormat, dep.Name(), j.partitionGroup(ctx), dep.partitionGroup(ctx))
+	}
+}
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 1c44c74..623c9dd 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -102,6 +102,7 @@
 				entries.SetString("LOCAL_MODULE_PATH", l.installDirPath.ToMakePath().String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.outputFilePath.Base())
 				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !installable)
+				entries.SetString("LINKER_CONFIG_PATH_"+l.Name(), l.OutputFile().String())
 			},
 		},
 	}}
diff --git a/python/androidmk.go b/python/androidmk.go
index 040b6be..60637d3 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -15,8 +15,6 @@
 package python
 
 import (
-	"fmt"
-	"io"
 	"path/filepath"
 	"strings"
 
@@ -24,86 +22,72 @@
 )
 
 type subAndroidMkProvider interface {
-	AndroidMk(*Module, *android.AndroidMkData)
+	AndroidMk(*Module, *android.AndroidMkEntries)
 }
 
-func (p *Module) subAndroidMk(data *android.AndroidMkData, obj interface{}) {
+func (p *Module) subAndroidMk(entries *android.AndroidMkEntries, obj interface{}) {
 	if p.subAndroidMkOnce == nil {
 		p.subAndroidMkOnce = make(map[subAndroidMkProvider]bool)
 	}
 	if androidmk, ok := obj.(subAndroidMkProvider); ok {
 		if !p.subAndroidMkOnce[androidmk] {
 			p.subAndroidMkOnce[androidmk] = true
-			androidmk.AndroidMk(p, data)
+			androidmk.AndroidMk(p, entries)
 		}
 	}
 }
 
-func (p *Module) AndroidMk() android.AndroidMkData {
-	ret := android.AndroidMkData{OutputFile: p.installSource}
+func (p *Module) AndroidMkEntries() []android.AndroidMkEntries {
+	entries := android.AndroidMkEntries{OutputFile: p.installSource}
 
-	p.subAndroidMk(&ret, p.installer)
+	p.subAndroidMk(&entries, p.installer)
 
-	return ret
+	return []android.AndroidMkEntries{entries}
 }
 
-func (p *binaryDecorator) AndroidMk(base *Module, ret *android.AndroidMkData) {
-	ret.Class = "EXECUTABLES"
+func (p *binaryDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
+	entries.Class = "EXECUTABLES"
 
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if len(p.binaryProperties.Test_suites) > 0 {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-				strings.Join(p.binaryProperties.Test_suites, " "))
-		}
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.AddCompatibilityTestSuites(p.binaryProperties.Test_suites...)
 	})
-	base.subAndroidMk(ret, p.pythonInstaller)
+	base.subAndroidMk(entries, p.pythonInstaller)
 }
 
-func (p *testDecorator) AndroidMk(base *Module, ret *android.AndroidMkData) {
-	ret.Class = "NATIVE_TESTS"
+func (p *testDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
+	entries.Class = "NATIVE_TESTS"
 
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if len(p.binaryDecorator.binaryProperties.Test_suites) > 0 {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-				strings.Join(p.binaryDecorator.binaryProperties.Test_suites, " "))
-		}
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.AddCompatibilityTestSuites(p.binaryDecorator.binaryProperties.Test_suites...)
 		if p.testConfig != nil {
-			fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=",
-				p.testConfig.String())
+			entries.SetString("LOCAL_FULL_TEST_CONFIG", p.testConfig.String())
 		}
 
-		if !BoolDefault(p.binaryProperties.Auto_gen_config, true) {
-			fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
-		}
+		entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(p.binaryProperties.Auto_gen_config, true))
 
-		if len(p.data) > 0 {
-			fmt.Fprintln(w, "LOCAL_TEST_DATA :=",
-				strings.Join(android.AndroidMkDataPaths(p.data), " "))
-		}
+		entries.AddStrings("LOCAL_TEST_DATA", android.AndroidMkDataPaths(p.data)...)
 
-		if Bool(p.testProperties.Test_options.Unit_test) {
-			fmt.Fprintln(w, "LOCAL_IS_UNIT_TEST := true")
-		}
+		entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(p.testProperties.Test_options.Unit_test))
 	})
-	base.subAndroidMk(ret, p.binaryDecorator.pythonInstaller)
+	base.subAndroidMk(entries, p.binaryDecorator.pythonInstaller)
 }
 
-func (installer *pythonInstaller) AndroidMk(base *Module, ret *android.AndroidMkData) {
+func (installer *pythonInstaller) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
 	// Soong installation is only supported for host modules. Have Make
 	// installation trigger Soong installation.
 	if base.Target().Os.Class == android.Host {
-		ret.OutputFile = android.OptionalPathForPath(installer.path)
+		entries.OutputFile = android.OptionalPathForPath(installer.path)
 	}
 
-	ret.Required = append(ret.Required, "libc++")
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
+	entries.Required = append(entries.Required, "libc++")
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		path, file := filepath.Split(installer.path.ToMakePath().String())
 		stem := strings.TrimSuffix(file, filepath.Ext(file))
 
-		fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+filepath.Ext(file))
-		fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path)
-		fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
-		fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(installer.androidMkSharedLibs, " "))
-		fmt.Fprintln(w, "LOCAL_CHECK_ELF_FILES := false")
+		entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
+		entries.SetString("LOCAL_MODULE_PATH", path)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
+		entries.AddStrings("LOCAL_SHARED_LIBRARIES", installer.androidMkSharedLibs...)
+		entries.SetBool("LOCAL_CHECK_ELF_FILES", false)
 	})
 }
diff --git a/python/python.go b/python/python.go
index c27a096..7e376c6 100644
--- a/python/python.go
+++ b/python/python.go
@@ -197,7 +197,7 @@
 
 var _ PythonDependency = (*Module)(nil)
 
-var _ android.AndroidMkDataProvider = (*Module)(nil)
+var _ android.AndroidMkEntriesProvider = (*Module)(nil)
 
 func (p *Module) Init() android.Module {
 
diff --git a/rust/androidmk.go b/rust/androidmk.go
index f98360a..c181d67 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -15,10 +15,7 @@
 package rust
 
 import (
-	"fmt"
-	"io"
 	"path/filepath"
-	"strings"
 
 	"android/soong/android"
 )
@@ -26,14 +23,14 @@
 type AndroidMkContext interface {
 	Name() string
 	Target() android.Target
-	SubAndroidMk(*android.AndroidMkData, interface{})
+	SubAndroidMk(*android.AndroidMkEntries, interface{})
 }
 
 type SubAndroidMkProvider interface {
-	AndroidMk(AndroidMkContext, *android.AndroidMkData)
+	AndroidMk(AndroidMkContext, *android.AndroidMkEntries)
 }
 
-func (mod *Module) SubAndroidMk(data *android.AndroidMkData, obj interface{}) {
+func (mod *Module) SubAndroidMk(data *android.AndroidMkEntries, obj interface{}) {
 	if mod.subAndroidMkOnce == nil {
 		mod.subAndroidMkOnce = make(map[SubAndroidMkProvider]bool)
 	}
@@ -45,33 +42,22 @@
 	}
 }
 
-func (mod *Module) AndroidMk() android.AndroidMkData {
-	if mod.Properties.HideFromMake {
-		return android.AndroidMkData{
-			Disabled: true,
-		}
+func (mod *Module) AndroidMkEntries() []android.AndroidMkEntries {
+	if mod.Properties.HideFromMake || mod.hideApexVariantFromMake {
+
+		return []android.AndroidMkEntries{android.AndroidMkEntries{Disabled: true}}
 	}
 
-	ret := android.AndroidMkData{
+	ret := android.AndroidMkEntries{
 		OutputFile: mod.outputFile,
 		Include:    "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
-				if len(mod.Properties.AndroidMkRlibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_RLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkRlibs, " "))
-				}
-				if len(mod.Properties.AndroidMkDylibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_DYLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkDylibs, " "))
-				}
-				if len(mod.Properties.AndroidMkProcMacroLibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_PROC_MACRO_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkProcMacroLibs, " "))
-				}
-				if len(mod.Properties.AndroidMkSharedLibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkSharedLibs, " "))
-				}
-				if len(mod.Properties.AndroidMkStaticLibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_STATIC_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkStaticLibs, " "))
-				}
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				entries.AddStrings("LOCAL_RLIB_LIBRARIES", mod.Properties.AndroidMkRlibs...)
+				entries.AddStrings("LOCAL_DYLIB_LIBRARIES", mod.Properties.AndroidMkDylibs...)
+				entries.AddStrings("LOCAL_PROC_MACRO_LIBRARIES", mod.Properties.AndroidMkProcMacroLibs...)
+				entries.AddStrings("LOCAL_SHARED_LIBRARIES", mod.Properties.AndroidMkSharedLibs...)
+				entries.AddStrings("LOCAL_STATIC_LIBRARIES", mod.Properties.AndroidMkStaticLibs...)
 			},
 		},
 	}
@@ -84,10 +70,10 @@
 	}
 	ret.SubName += mod.Properties.SubName
 
-	return ret
+	return []android.AndroidMkEntries{ret}
 }
 
-func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, binary.baseCompiler)
 
 	if binary.distFile.Valid() {
@@ -95,35 +81,26 @@
 	}
 
 	ret.Class = "EXECUTABLES"
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if binary.coverageOutputZipFile.Valid() {
-			fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+binary.coverageOutputZipFile.String())
-		}
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.SetOptionalPath("LOCAL_PREBUILT_COVERAGE_ARCHIVE", binary.coverageOutputZipFile)
 	})
 }
 
-func (test *testDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (test *testDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	test.binaryDecorator.AndroidMk(ctx, ret)
 	ret.Class = "NATIVE_TESTS"
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if len(test.Properties.Test_suites) > 0 {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-				strings.Join(test.Properties.Test_suites, " "))
-		}
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
 		if test.testConfig != nil {
-			fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=", test.testConfig.String())
+			entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
 		}
-		if !BoolDefault(test.Properties.Auto_gen_config, true) {
-			fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
-		}
-		if Bool(test.Properties.Test_options.Unit_test) {
-			fmt.Fprintln(w, "LOCAL_IS_UNIT_TEST := true")
-		}
+		entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(test.Properties.Auto_gen_config, true))
+		entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(test.Properties.Test_options.Unit_test))
 	})
 	// TODO(chh): add test data with androidMkWriteTestData(test.data, ctx, ret)
 }
 
-func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, library.baseCompiler)
 
 	if library.rlib() {
@@ -140,15 +117,12 @@
 		ret.DistFiles = android.MakeDefaultDistFiles(library.distFile.Path())
 	}
 
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if library.coverageOutputZipFile.Valid() {
-			fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+library.coverageOutputZipFile.String())
-		}
-
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.SetOptionalPath("LOCAL_PREBUILT_COVERAGE_ARCHIVE", library.coverageOutputZipFile)
 	})
 }
 
-func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, procMacro.baseCompiler)
 
 	ret.Class = "PROC_MACRO_LIBRARIES"
@@ -158,29 +132,29 @@
 
 }
 
-func (sourceProvider *BaseSourceProvider) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (sourceProvider *BaseSourceProvider) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	outFile := sourceProvider.OutputFiles[0]
 	ret.Class = "ETC"
 	ret.OutputFile = android.OptionalPathForPath(outFile)
 	ret.SubName += sourceProvider.subName
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		_, file := filepath.Split(outFile.String())
 		stem, suffix, _ := android.SplitFileExt(file)
-		fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+suffix)
-		fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
-		fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
+		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
+		entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
 	})
 }
 
-func (bindgen *bindgenDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (bindgen *bindgenDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, bindgen.BaseSourceProvider)
 }
 
-func (proto *protobufDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (proto *protobufDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, proto.BaseSourceProvider)
 }
 
-func (compiler *baseCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (compiler *baseCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	if compiler.path == (android.InstallPath{}) {
 		return
 	}
@@ -194,14 +168,12 @@
 		unstrippedOutputFile = ret.OutputFile
 		ret.OutputFile = compiler.strippedOutputFile
 	}
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if compiler.strippedOutputFile.Valid() {
-			fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", unstrippedOutputFile)
-		}
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.SetOptionalPath("LOCAL_SOONG_UNSTRIPPED_BINARY", unstrippedOutputFile)
 		path, file := filepath.Split(compiler.path.ToMakePath().String())
 		stem, suffix, _ := android.SplitFileExt(file)
-		fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+suffix)
-		fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path)
-		fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
+		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+		entries.SetString("LOCAL_MODULE_PATH", path)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
 	})
 }
diff --git a/rust/clippy_test.go b/rust/clippy_test.go
index 132b7b8..e24f666 100644
--- a/rust/clippy_test.go
+++ b/rust/clippy_test.go
@@ -18,6 +18,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/cc"
 )
 
 func TestClippy(t *testing.T) {
@@ -45,6 +46,7 @@
 		}`
 
 	bp = bp + GatherRequiredDepsForTest()
+	bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
 
 	fs := map[string][]byte{
 		// Reuse the same blueprint file for subdirectories.
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index a6dba9e..2b40727 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -19,6 +19,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/cc"
 )
 
 // Test that feature flags are being correctly generated.
@@ -132,6 +133,7 @@
 		}`
 
 	bp = bp + GatherRequiredDepsForTest()
+	bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
 
 	fs := map[string][]byte{
 		// Reuse the same blueprint file for subdirectories.
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 3ad32fa..e6643f5 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -6,6 +6,7 @@
 	// for an example.
 	// TODO(b/160223496): enable rustfmt globally.
 	RustAllowedPaths = []string{
+		"device/google/cuttlefish",
 		"external/adhd",
 		"external/crosvm",
 		"external/minijail",
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{
diff --git a/rust/library.go b/rust/library.go
index ae33f0f..971588d 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -428,6 +428,7 @@
 	var srcPath android.Path
 
 	if library.sourceProvider != nil {
+		// Assume the first source from the source provider is the library entry point.
 		srcPath = library.sourceProvider.Srcs()[0]
 	} else {
 		srcPath, _ = srcPathFromModuleSrcs(ctx, library.baseCompiler.Properties.Srcs)
diff --git a/rust/project_json.go b/rust/project_json.go
index 5697408..8d9e50c 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -109,6 +109,10 @@
 		if !ok {
 			return
 		}
+		// Skip intra-module dependencies (i.e., generated-source library depending on the source variant).
+		if module.Name() == child.Name() {
+			return
+		}
 		if _, ok = deps[ctx.ModuleName(child)]; ok {
 			return
 		}
diff --git a/rust/project_json_test.go b/rust/project_json_test.go
index 69288fc..16699c1 100644
--- a/rust/project_json_test.go
+++ b/rust/project_json_test.go
@@ -131,6 +131,22 @@
 			t.Errorf("The source path for libbindings2 does not contain the BuildOs, got %v; want %v",
 				rootModule, android.BuildOs.String())
 		}
+		// Check that libbindings1 does not depend on itself.
+		if strings.Contains(rootModule, "libbindings1") {
+			deps, ok := crate["deps"].([]interface{})
+			if !ok {
+				t.Errorf("Unexpected format for deps: %v", crate["deps"])
+			}
+			for _, dep := range deps {
+				d, ok := dep.(map[string]interface{})
+				if !ok {
+					t.Errorf("Unexpected format for dep: %v", dep)
+				}
+				if d["name"] == "bindings1" {
+					t.Errorf("libbindings1 depends on itself")
+				}
+			}
+		}
 	}
 }
 
diff --git a/rust/protobuf.go b/rust/protobuf.go
index 7d6e1fd..235b4ad 100644
--- a/rust/protobuf.go
+++ b/rust/protobuf.go
@@ -46,8 +46,8 @@
 var _ SourceProvider = (*protobufDecorator)(nil)
 
 type ProtobufProperties struct {
-	// Path to the proto file that will be used to generate the source
-	Proto *string `android:"path,arch_variant"`
+	// List of realtive paths to proto files that will be used to generate the source
+	Protos []string `android:"path,arch_variant"`
 
 	// List of additional flags to pass to aprotoc
 	Proto_flags []string `android:"arch_variant"`
@@ -66,6 +66,7 @@
 func (proto *protobufDecorator) GenerateSource(ctx ModuleContext, deps PathDeps) android.Path {
 	var protoFlags android.ProtoFlags
 	var pluginPaths android.Paths
+	var protoNames []string
 
 	protoFlags.OutTypeFlag = "--rust_out"
 	outDir := android.PathForModuleOut(ctx)
@@ -77,10 +78,7 @@
 
 	protoFlags.Deps = append(protoFlags.Deps, pluginPaths...)
 
-	protoFile := android.OptionalPathForModuleSrc(ctx, proto.Properties.Proto)
-	if !protoFile.Valid() {
-		ctx.PropertyErrorf("proto", "invalid path to proto file")
-	}
+	protoFiles := android.PathsForModuleSrc(ctx, proto.Properties.Protos)
 
 	// Add exported dependency include paths
 	for _, include := range deps.depIncludePaths {
@@ -88,35 +86,58 @@
 	}
 
 	stem := proto.BaseSourceProvider.getStem(ctx)
-	// rust protobuf-codegen output <stem>.rs
-	stemFile := android.PathForModuleOut(ctx, stem+".rs")
-	// add mod_<stem>.rs to import <stem>.rs
-	modFile := android.PathForModuleOut(ctx, "mod_"+stem+".rs")
-	// mod_<stem>.rs is the main/first output file to be included/compiled
-	outputs := android.WritablePaths{modFile, stemFile}
-	if proto.plugin == Grpc {
-		outputs = append(outputs, android.PathForModuleOut(ctx, stem+grpcSuffix+".rs"))
-	}
-	depFile := android.PathForModuleOut(ctx, "mod_"+stem+".d")
+
+	// The mod_stem.rs file is used to avoid collisions if this is not included as a crate.
+	stemFile := android.PathForModuleOut(ctx, "mod_"+stem+".rs")
+
+	// stemFile must be first here as the first path in BaseSourceProvider.OutputFiles is the library entry-point.
+	outputs := android.WritablePaths{stemFile}
 
 	rule := android.NewRuleBuilder()
-	android.ProtoRule(ctx, rule, protoFile.Path(), protoFlags, protoFlags.Deps, outDir, depFile, outputs)
-	rule.Command().Text("printf '" + proto.getModFileContents(ctx) + "' >").Output(modFile)
-	rule.Build(pctx, ctx, "protoc_"+protoFile.Path().Rel(), "protoc "+protoFile.Path().Rel())
+	for _, protoFile := range protoFiles {
+		protoName := strings.TrimSuffix(protoFile.Base(), ".proto")
+		protoNames = append(protoNames, protoName)
 
-	proto.BaseSourceProvider.OutputFiles = android.Paths{modFile, stemFile}
-	return modFile
+		protoOut := android.PathForModuleOut(ctx, protoName+".rs")
+		ruleOutputs := android.WritablePaths{android.WritablePath(protoOut)}
+
+		if proto.plugin == Grpc {
+			grpcOut := android.PathForModuleOut(ctx, protoName+grpcSuffix+".rs")
+			ruleOutputs = append(ruleOutputs, android.WritablePath(grpcOut))
+		}
+
+		depFile := android.PathForModuleOut(ctx, protoName+".d")
+
+		android.ProtoRule(ctx, rule, protoFile, protoFlags, protoFlags.Deps, outDir, depFile, ruleOutputs)
+		outputs = append(outputs, ruleOutputs...)
+	}
+
+	rule.Command().
+		Implicits(outputs.Paths()).
+		Text("printf '" + proto.genModFileContents(ctx, protoNames) + "' >").
+		Output(stemFile)
+
+	rule.Build(pctx, ctx, "protoc_"+ctx.ModuleName(), "protoc "+ctx.ModuleName())
+
+	proto.BaseSourceProvider.OutputFiles = outputs.Paths()
+
+	// mod_stem.rs is the entry-point for our library modules, so this is what we return.
+	return stemFile
 }
 
-func (proto *protobufDecorator) getModFileContents(ctx ModuleContext) string {
-	stem := proto.BaseSourceProvider.getStem(ctx)
+func (proto *protobufDecorator) genModFileContents(ctx ModuleContext, protoNames []string) string {
 	lines := []string{
-		"// @generated",
-		fmt.Sprintf("pub mod %s;", stem),
+		"// @Soong generated Source",
+	}
+	for _, protoName := range protoNames {
+		lines = append(lines, fmt.Sprintf("pub mod %s;", protoName))
+
+		if proto.plugin == Grpc {
+			lines = append(lines, fmt.Sprintf("pub mod %s%s;", protoName, grpcSuffix))
+		}
 	}
 
 	if proto.plugin == Grpc {
-		lines = append(lines, fmt.Sprintf("pub mod %s%s;", stem, grpcSuffix))
 		lines = append(
 			lines,
 			"pub mod empty {",
diff --git a/rust/protobuf_test.go b/rust/protobuf_test.go
index 845911f..608a4e8 100644
--- a/rust/protobuf_test.go
+++ b/rust/protobuf_test.go
@@ -25,7 +25,7 @@
 	ctx := testRust(t, `
 		rust_protobuf {
 			name: "librust_proto",
-			proto: "buf.proto",
+			protos: ["buf.proto", "proto.proto"],
 			crate_name: "rust_proto",
 			source_stem: "buf",
 			shared_libs: ["libfoo_shared"],
@@ -60,13 +60,20 @@
 	if w := "-Istatic_include"; !strings.Contains(cmd, w) {
 		t.Errorf("expected %q in %q", w, cmd)
 	}
+
+	// Check proto.rs, the second protobuf, is listed as an output
+	librust_proto_outputs := ctx.ModuleForTests("librust_proto", "android_arm64_armv8-a_source").AllOutputs()
+	if android.InList("proto.rs", librust_proto_outputs) {
+		t.Errorf("rust_protobuf is not producing multiple outputs; expected 'proto.rs' in list, got: %#v ",
+			librust_proto_outputs)
+	}
 }
 
 func TestRustGrpcio(t *testing.T) {
 	ctx := testRust(t, `
 		rust_grpcio {
 			name: "librust_grpcio",
-			proto: "buf.proto",
+			protos: ["buf.proto", "proto.proto"],
 			crate_name: "rust_grpcio",
 			source_stem: "buf",
 			shared_libs: ["libfoo_shared"],
@@ -117,4 +124,11 @@
 	if w := "-Ilibprotobuf-cpp-full-includes"; !strings.Contains(cmd, w) {
 		t.Errorf("expected %q in %q", w, cmd)
 	}
+
+	// Check proto.rs, the second protobuf, is listed as an output
+	librust_grpcio_outputs := ctx.ModuleForTests("librust_grpcio", "android_arm64_armv8-a_source").AllOutputs()
+	if android.InList("proto_grpc.rs", librust_grpcio_outputs) {
+		t.Errorf("rust_protobuf is not producing multiple outputs; expected 'proto_grpc.rs' in list, got: %#v ",
+			librust_grpcio_outputs)
+	}
 }
diff --git a/rust/rust.go b/rust/rust.go
index 5b94045..38caad3 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -74,6 +74,7 @@
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
+	android.ApexModuleBase
 
 	Properties BaseProperties
 
@@ -88,6 +89,8 @@
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 
 	outputFile android.OptionalPath
+
+	hideApexVariantFromMake bool
 }
 
 func (mod *Module) OutputFiles(tag string) (android.Paths, error) {
@@ -508,6 +511,7 @@
 	}
 
 	android.InitAndroidArchModule(mod, mod.hod, mod.multilib)
+	android.InitApexModule(mod)
 
 	android.InitDefaultableModule(mod)
 	return mod
@@ -605,6 +609,11 @@
 		ModuleContext: actx,
 	}
 
+	apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	if !apexInfo.IsForPlatform() {
+		mod.hideApexVariantFromMake = true
+	}
+
 	toolchain := mod.toolchain(ctx)
 
 	if !toolchain.Supported() {
@@ -685,6 +694,14 @@
 	proc_macro bool
 }
 
+// InstallDepNeeded returns true for rlibs, dylibs, and proc macros so that they or their transitive
+// dependencies (especially C/C++ shared libs) are installed as dependencies of a rust binary.
+func (d dependencyTag) InstallDepNeeded() bool {
+	return d.library || d.proc_macro
+}
+
+var _ android.InstallNeededDependencyTag = dependencyTag{}
+
 var (
 	customBindgenDepTag = dependencyTag{name: "customBindgenTag"}
 	rlibDepTag          = dependencyTag{name: "rlibTag", library: true}
@@ -694,6 +711,11 @@
 	sourceDepTag        = dependencyTag{name: "source"}
 )
 
+func IsDylibDepTag(depTag blueprint.DependencyTag) bool {
+	tag, ok := depTag.(dependencyTag)
+	return ok && tag == dylibDepTag
+}
+
 type autoDep struct {
 	variation string
 	depTag    dependencyTag
@@ -1052,6 +1074,58 @@
 	return android.OptionalPath{}
 }
 
+var _ android.ApexModule = (*Module)(nil)
+
+func (mod *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
+	return nil
+}
+
+func (mod *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
+	depTag := ctx.OtherModuleDependencyTag(dep)
+
+	if ccm, ok := dep.(*cc.Module); ok {
+		if ccm.HasStubsVariants() {
+			if cc.IsSharedDepTag(depTag) {
+				// dynamic dep to a stubs lib crosses APEX boundary
+				return false
+			}
+			if cc.IsRuntimeDepTag(depTag) {
+				// runtime dep to a stubs lib also crosses APEX boundary
+				return false
+			}
+
+			if cc.IsHeaderDepTag(depTag) {
+				return false
+			}
+		}
+		if mod.Static() && cc.IsSharedDepTag(depTag) {
+			// shared_lib dependency from a static lib is considered as crossing
+			// the APEX boundary because the dependency doesn't actually is
+			// linked; the dependency is used only during the compilation phase.
+			return false
+		}
+	}
+
+	if depTag == procMacroDepTag {
+		return false
+	}
+
+	return true
+}
+
+// Overrides ApexModule.IsInstallabeToApex()
+func (mod *Module) IsInstallableToApex() bool {
+	if mod.compiler != nil {
+		if lib, ok := mod.compiler.(*libraryDecorator); ok && (lib.shared() || lib.dylib()) {
+			return true
+		}
+		if _, ok := mod.compiler.(*binaryDecorator); ok {
+			return true
+		}
+	}
+	return false
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 187f0b6..4edc6cd 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -106,13 +106,14 @@
 // useMockedFs setup a default mocked filesystem for the test environment.
 func (tctx *testRustCtx) useMockedFs() {
 	tctx.fs = map[string][]byte{
-		"foo.rs":     nil,
-		"foo.c":      nil,
-		"src/bar.rs": nil,
-		"src/any.h":  nil,
-		"buf.proto":  nil,
-		"liby.so":    nil,
-		"libz.so":    nil,
+		"foo.rs":      nil,
+		"foo.c":       nil,
+		"src/bar.rs":  nil,
+		"src/any.h":   nil,
+		"proto.proto": nil,
+		"buf.proto":   nil,
+		"liby.so":     nil,
+		"libz.so":     nil,
 	}
 }
 
@@ -120,6 +121,7 @@
 // attributes of the testRustCtx.
 func (tctx *testRustCtx) generateConfig() {
 	tctx.bp = tctx.bp + GatherRequiredDepsForTest()
+	tctx.bp = tctx.bp + cc.GatherRequiredDepsForTest(android.NoOsType)
 	cc.GatherRequiredFilesForTest(tctx.fs)
 	config := android.TestArchConfig(buildDir, tctx.env, tctx.bp, tctx.fs)
 	tctx.config = &config
diff --git a/rust/source_provider.go b/rust/source_provider.go
index 436518c..7719611 100644
--- a/rust/source_provider.go
+++ b/rust/source_provider.go
@@ -30,6 +30,7 @@
 type BaseSourceProvider struct {
 	Properties SourceProviderProperties
 
+	// The first file in OutputFiles must be the library entry point.
 	OutputFiles      android.Paths
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 	subName          string
diff --git a/rust/testing.go b/rust/testing.go
index 001f322..a8496d9 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -77,6 +77,7 @@
 			no_libcrt: true,
 			nocrt: true,
 			system_shared_libs: [],
+			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 		}
 		cc_library {
 			name: "libprotobuf-cpp-full",
@@ -93,6 +94,7 @@
 			host_supported: true,
                         native_coverage: false,
 			sysroot: true,
+			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 		}
 		rust_library {
 			name: "libtest",
@@ -102,6 +104,7 @@
 			host_supported: true,
                         native_coverage: false,
 			sysroot: true,
+			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 		}
 		rust_library {
 			name: "libprotobuf",
@@ -122,15 +125,11 @@
 			host_supported: true,
 		}
 
-` + cc.GatherRequiredDepsForTest(android.NoOsType)
+`
 	return bp
 }
 
-func CreateTestContext(config android.Config) *android.TestContext {
-	ctx := android.NewTestArchContext(config)
-	android.RegisterPrebuiltMutators(ctx)
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
+func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
 	ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
 	ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory)
@@ -164,6 +163,14 @@
 		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
 	})
 	ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+}
+
+func CreateTestContext(config android.Config) *android.TestContext {
+	ctx := android.NewTestArchContext(config)
+	android.RegisterPrebuiltMutators(ctx)
+	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+	cc.RegisterRequiredBuildComponentsForTest(ctx)
+	RegisterRequiredBuildComponentsForTest(ctx)
 
 	return ctx
 }
diff --git a/scripts/conv_linker_config.py b/scripts/conv_linker_config.py
index 81425fb..fca71ad 100644
--- a/scripts/conv_linker_config.py
+++ b/scripts/conv_linker_config.py
@@ -18,8 +18,10 @@
 import argparse
 import collections
 import json
+import os
 
 import linker_config_pb2
+from google.protobuf.descriptor import FieldDescriptor
 from google.protobuf.json_format import ParseDict
 from google.protobuf.text_format import MessageToString
 
@@ -43,6 +45,25 @@
   print(MessageToString(pb))
 
 
+def SystemProvide(args):
+  pb = linker_config_pb2.LinkerConfig()
+  with open(args.source, 'rb') as f:
+    pb.ParseFromString(f.read())
+  libraries = args.value.split()
+
+  def IsInLibPath(lib_name):
+    lib_path = os.path.join(args.system, 'lib', lib_name)
+    lib64_path = os.path.join(args.system, 'lib64', lib_name)
+    return os.path.exists(lib_path) or os.path.islink(lib_path) or os.path.exists(lib64_path) or os.path.islink(lib64_path)
+
+  installed_libraries = list(filter(IsInLibPath, libraries))
+  for item in installed_libraries:
+    if item not in getattr(pb, 'provideLibs'):
+      getattr(pb, 'provideLibs').append(item)
+  with open(args.output, 'wb') as f:
+    f.write(pb.SerializeToString())
+
+
 def GetArgParser():
   parser = argparse.ArgumentParser()
   subparsers = parser.add_subparsers()
@@ -73,6 +94,32 @@
       help='Source linker configuration file in protobuf.')
   print_proto.set_defaults(func=Print)
 
+  system_provide_libs = subparsers.add_parser(
+      'systemprovide', help='Append system provide libraries into the configuration.')
+  system_provide_libs.add_argument(
+      '-s',
+      '--source',
+      required=True,
+      type=str,
+      help='Source linker configuration file in protobuf.')
+  system_provide_libs.add_argument(
+      '-o',
+      '--output',
+      required=True,
+      type=str,
+      help='Target linker configuration file to write in protobuf.')
+  system_provide_libs.add_argument(
+      '--value',
+      required=True,
+      type=str,
+      help='Values of the libraries to append. If there are more than one it should be separated by empty space')
+  system_provide_libs.add_argument(
+      '--system',
+      required=True,
+      type=str,
+      help='Path of the system image.')
+  system_provide_libs.set_defaults(func=SystemProvide)
+
   return parser
 
 
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 7e5c344..f86e1fd 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -398,7 +398,7 @@
 			func(entries *android.AndroidMkEntries) {
 				s.customAndroidMkEntries(entries)
 				entries.SetPath("LOCAL_MODULE_PATH", s.installDir.ToMakePath())
-				entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", s.testProperties.Test_suites...)
+				entries.AddCompatibilityTestSuites(s.testProperties.Test_suites...)
 				if s.testConfig != nil {
 					entries.SetPath("LOCAL_FULL_TEST_CONFIG", s.testConfig)
 				}
diff --git a/ui/build/build.go b/ui/build/build.go
index cfd0b83..da088e4 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -44,7 +44,7 @@
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir"))
 
 	if buildDateTimeFile, ok := config.environ.Get("BUILD_DATETIME_FILE"); ok {
-		err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0777)
+		err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0666) // a+rw
 		if err != nil {
 			ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err)
 		}
@@ -85,6 +85,7 @@
 	}
 }
 
+// These are bitmasks which can be used to check whether various flags are set e.g. whether to use Bazel.
 const (
 	BuildNone          = iota
 	BuildProductConfig = 1 << iota
@@ -97,6 +98,7 @@
 	BuildAllWithBazel  = BuildProductConfig | BuildSoong | BuildKati | BuildBazel
 )
 
+// checkProblematicFiles fails the build if existing Android.mk or CleanSpec.mk files are found at the root of the tree.
 func checkProblematicFiles(ctx Context) {
 	files := []string{"Android.mk", "CleanSpec.mk"}
 	for _, file := range files {
@@ -108,6 +110,7 @@
 	}
 }
 
+// checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
 func checkCaseSensitivity(ctx Context, config Config) {
 	outDir := config.OutDir()
 	lowerCase := filepath.Join(outDir, "casecheck.txt")
@@ -115,13 +118,11 @@
 	lowerData := "a"
 	upperData := "B"
 
-	err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0777)
-	if err != nil {
+	if err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0666); err != nil { // a+rw
 		ctx.Fatalln("Failed to check case sensitivity:", err)
 	}
 
-	err = ioutil.WriteFile(upperCase, []byte(upperData), 0777)
-	if err != nil {
+	if err := ioutil.WriteFile(upperCase, []byte(upperData), 0666); err != nil { // a+rw
 		ctx.Fatalln("Failed to check case sensitivity:", err)
 	}
 
@@ -139,18 +140,15 @@
 	}
 }
 
-func help(ctx Context, config Config, what int) {
+// help prints a help/usage message, via the build/make/help.sh script.
+func help(ctx Context, config Config) {
 	cmd := Command(ctx, config, "help.sh", "build/make/help.sh")
 	cmd.Sandbox = dumpvarsSandbox
 	cmd.RunAndPrintOrFatal()
 }
 
-// Build the tree. The 'what' argument can be used to chose which components of
-// the build to run.
-func Build(ctx Context, config Config, what int) {
-	ctx.Verboseln("Starting build with args:", config.Arguments())
-	ctx.Verboseln("Environment:", config.Environment().Environ())
-
+// checkRAM warns if there probably isn't enough RAM to complete a build.
+func checkRAM(ctx Context, config Config) {
 	if totalRAM := config.TotalRAM(); totalRAM != 0 {
 		ram := float32(totalRAM) / (1024 * 1024 * 1024)
 		ctx.Verbosef("Total RAM: %.3vGB", ram)
@@ -166,24 +164,24 @@
 			ctx.Println("-j value.")
 			ctx.Println("************************************************************")
 		} else if ram <= float32(config.Parallel()) {
+			// Want at least 1GB of RAM per job.
 			ctx.Printf("Warning: high -j%d count compared to %.3vGB of RAM", config.Parallel(), ram)
 			ctx.Println("If you run into segfaults or other errors, try a lower -j value")
 		}
 	}
+}
+
+// Build the tree. The 'what' argument can be used to chose which components of
+// the build to run, via checking various bitmasks.
+func Build(ctx Context, config Config, what int) {
+	ctx.Verboseln("Starting build with args:", config.Arguments())
+	ctx.Verboseln("Environment:", config.Environment().Environ())
 
 	ctx.BeginTrace(metrics.Total, "total")
 	defer ctx.EndTrace()
 
-	if config.SkipMake() {
-		ctx.Verboseln("Skipping Make/Kati as requested")
-		what = what & (BuildSoong | BuildNinja)
-	}
-
 	if inList("help", config.Arguments()) {
-		help(ctx, config, what)
-		return
-	} else if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
-		clean(ctx, config)
+		help(ctx, config)
 		return
 	}
 
@@ -191,16 +189,31 @@
 	buildLock := BecomeSingletonOrFail(ctx, config)
 	defer buildLock.Unlock()
 
+	if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
+		clean(ctx, config)
+		return
+	}
+
+	// checkProblematicFiles aborts the build if Android.mk or CleanSpec.mk are found at the root of the tree.
 	checkProblematicFiles(ctx)
 
+	checkRAM(ctx, config)
+
 	SetupOutDir(ctx, config)
 
+	// checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
 	checkCaseSensitivity(ctx, config)
 
 	ensureEmptyDirectoriesExist(ctx, config.TempDir())
 
 	SetupPath(ctx, config)
 
+	if config.SkipMake() {
+		ctx.Verboseln("Skipping Make/Kati as requested")
+		// If Make/Kati is disabled, then explicitly build using Soong and Ninja.
+		what = what & (BuildSoong | BuildNinja)
+	}
+
 	if config.StartGoma() {
 		// Ensure start Goma compiler_proxy
 		startGoma(ctx, config)
@@ -216,12 +229,16 @@
 		runMakeProductConfig(ctx, config)
 	}
 
+	// Everything below here depends on product config.
+
 	if inList("installclean", config.Arguments()) ||
 		inList("install-clean", config.Arguments()) {
 		installClean(ctx, config)
 		ctx.Println("Deleted images and staging directories.")
 		return
-	} else if inList("dataclean", config.Arguments()) ||
+	}
+
+	if inList("dataclean", config.Arguments()) ||
 		inList("data-clean", config.Arguments()) {
 		dataClean(ctx, config)
 		ctx.Println("Deleted data files.")
@@ -240,7 +257,7 @@
 		runKatiBuild(ctx, config)
 		runKatiPackage(ctx, config)
 
-		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0777)
+		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
 	} else {
 		// Load last Kati Suffix if it exists
 		if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
@@ -267,6 +284,7 @@
 		runNinja(ctx, config)
 	}
 
+	// Currently, using Bazel requires Kati and Soong to run first, so check whether to run Bazel last.
 	if what&BuildBazel != 0 {
 		runBazel(ctx, config)
 	}
@@ -282,14 +300,11 @@
 	subDir := filepath.Join(subDirs...)
 	destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
 
-	err := os.MkdirAll(destDir, 0777)
-	if err != nil {
+	if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
 		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
-
 	}
 
-	err = gzipFileToDir(src, destDir)
-	if err != nil {
+	if err := gzipFileToDir(src, destDir); err != nil {
 		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
 	}
 }
@@ -304,14 +319,11 @@
 	subDir := filepath.Join(subDirs...)
 	destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
 
-	err := os.MkdirAll(destDir, 0777)
-	if err != nil {
+	if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
 		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
-
 	}
 
-	_, err = copyFile(src, filepath.Join(destDir, filepath.Base(src)))
-	if err != nil {
+	if _, err := copyFile(src, filepath.Join(destDir, filepath.Base(src))); err != nil {
 		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
 	}
 }
diff --git a/ui/build/upload.go b/ui/build/upload.go
index a9346e0..4f30136 100644
--- a/ui/build/upload.go
+++ b/ui/build/upload.go
@@ -30,32 +30,34 @@
 )
 
 const (
+	// Used to generate a raw protobuf file that contains information
+	// of the list of metrics files from host to destination storage.
 	uploadPbFilename = ".uploader.pb"
 )
 
 var (
-	// For testing purpose
-	getTmpDir = ioutil.TempDir
+	// For testing purpose.
+	tmpDir = ioutil.TempDir
 )
 
 // UploadMetrics uploads a set of metrics files to a server for analysis. An
-// uploader full path is required to be specified in order to upload the set
-// of metrics files. This is accomplished by defining the ANDROID_ENABLE_METRICS_UPLOAD
-// environment variable. The metrics files are copied to a temporary directory
-// and the uploader is then executed in the background to allow the user to continue
-// working.
+// uploader full path is specified in ANDROID_ENABLE_METRICS_UPLOAD environment
+// variable in order to upload the set of metrics files. The metrics files are
+// first copied to a temporary directory and the uploader is then executed in
+// the background to allow the user/system to continue working. Soong communicates
+// to the uploader through the upload_proto raw protobuf file.
 func UploadMetrics(ctx Context, config Config, simpleOutput bool, buildStarted time.Time, files ...string) {
 	ctx.BeginTrace(metrics.RunSetupTool, "upload_metrics")
 	defer ctx.EndTrace()
 
 	uploader := config.MetricsUploaderApp()
-	// No metrics to upload if the path to the uploader was not specified.
 	if uploader == "" {
+		// If the uploader path was not specified, no metrics shall be uploaded.
 		return
 	}
 
-	// Some files may not exist. For example, build errors protobuf file
-	// may not exist since the build was successful.
+	// Some files passed in to this function may not exist. For example,
+	// build errors protobuf file may not exist since the build was successful.
 	var metricsFiles []string
 	for _, f := range files {
 		if _, err := os.Stat(f); err == nil {
@@ -70,7 +72,7 @@
 	// The temporary directory cannot be deleted as the metrics uploader is started
 	// in the background and requires to exist until the operation is done. The
 	// uploader can delete the directory as it is specified in the upload proto.
-	tmpDir, err := getTmpDir("", "upload_metrics")
+	tmpDir, err := tmpDir("", "upload_metrics")
 	if err != nil {
 		ctx.Fatalf("failed to create a temporary directory to store the list of metrics files: %v\n", err)
 	}
@@ -103,7 +105,7 @@
 	}
 
 	// Start the uploader in the background as it takes several milliseconds to start the uploader
-	// and prepare the metrics for upload. This affects small commands like "lunch".
+	// and prepare the metrics for upload. This affects small shell commands like "lunch".
 	cmd := Command(ctx, config, "upload metrics", uploader, "--upload-metrics", pbFile)
 	if simpleOutput {
 		cmd.RunOrFatal()
diff --git a/ui/build/upload_test.go b/ui/build/upload_test.go
index dccf156..768b031 100644
--- a/ui/build/upload_test.go
+++ b/ui/build/upload_test.go
@@ -62,16 +62,16 @@
 			}
 			defer os.RemoveAll(outDir)
 
-			// Supply our own getTmpDir to delete the temp dir once the test is done.
-			orgGetTmpDir := getTmpDir
-			getTmpDir = func(string, string) (string, error) {
+			// Supply our own tmpDir to delete the temp dir once the test is done.
+			orgTmpDir := tmpDir
+			tmpDir = func(string, string) (string, error) {
 				retDir := filepath.Join(outDir, "tmp_upload_dir")
 				if err := os.Mkdir(retDir, 0755); err != nil {
 					t.Fatalf("failed to create temporary directory %q: %v", retDir, err)
 				}
 				return retDir, nil
 			}
-			defer func() { getTmpDir = orgGetTmpDir }()
+			defer func() { tmpDir = orgTmpDir }()
 
 			metricsUploadDir := filepath.Join(outDir, ".metrics_uploader")
 			if err := os.Mkdir(metricsUploadDir, 0755); err != nil {
@@ -134,11 +134,11 @@
 			}
 			defer os.RemoveAll(outDir)
 
-			orgGetTmpDir := getTmpDir
-			getTmpDir = func(string, string) (string, error) {
+			orgTmpDir := tmpDir
+			tmpDir = func(string, string) (string, error) {
 				return tt.tmpDir, tt.tmpDirErr
 			}
-			defer func() { getTmpDir = orgGetTmpDir }()
+			defer func() { tmpDir = orgTmpDir }()
 
 			metricsFile := filepath.Join(outDir, "metrics_file_1")
 			if err := ioutil.WriteFile(metricsFile, []byte("test file"), 0644); err != nil {