Merge "Skip MIPS headers when generating NDK prebuilts"
diff --git a/Android.bp b/Android.bp
index e89f908..d32c957 100644
--- a/Android.bp
+++ b/Android.bp
@@ -213,6 +213,7 @@
         "java/app.go",
         "java/builder.go",
         "java/gen.go",
+        "java/jacoco.go",
         "java/java.go",
         "java/proto.go",
         "java/resources.go",
diff --git a/android/variable.go b/android/variable.go
index 4272817..13b5abf 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -189,6 +189,7 @@
 	Override_rs_driver *string `json:",omitempty"`
 
 	DeviceKernelHeaders []string `json:",omitempty"`
+	DistDir             *string  `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/cc/builder.go b/cc/builder.go
index a81dc89..b5f4c5c 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -196,11 +196,19 @@
 
 	_ = pctx.SourcePathVariable("sAbiDiffer", "prebuilts/build-tools/${config.HostPrebuiltTag}/bin/header-abi-diff")
 
-	// Abidiff check turned on in advice-only mode. Builds will not fail on abi incompatibilties / extensions.
-	sAbiDiff = pctx.AndroidStaticRule("sAbiDiff",
-		blueprint.RuleParams{
-			Command:     "$sAbiDiffer $allowFlags -lib $libName -arch $arch -check-all-apis -o ${out} -new $in -old $referenceDump",
-			CommandDeps: []string{"$sAbiDiffer"},
+	sAbiDiff = pctx.AndroidRuleFunc("sAbiDiff",
+		func(config android.Config) (blueprint.RuleParams, error) {
+
+			commandStr := "($sAbiDiffer $allowFlags -lib $libName -arch $arch -check-all-apis -o ${out} -new $in -old $referenceDump)"
+			distDir := config.ProductVariables.DistDir
+			if distDir != nil && *distDir != "" {
+				distAbiDiffDir := *distDir + "/abidiffs/"
+				commandStr += "  || (mkdir -p " + distAbiDiffDir + " && cp ${out} " + distAbiDiffDir + " && exit 1)"
+			}
+			return blueprint.RuleParams{
+				Command:     commandStr,
+				CommandDeps: []string{"$sAbiDiffer"},
+			}, nil
 		},
 		"allowFlags", "referenceDump", "libName", "arch")
 
diff --git a/cc/cc.go b/cc/cc.go
index 384b240..e18b2cc 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -326,9 +326,12 @@
 	flags Flags
 
 	// When calling a linker, if module A depends on module B, then A must precede B in its command
-	// line invocation. staticDepsInLinkOrder stores the proper ordering of all of the transitive
+	// line invocation. depsInLinkOrder stores the proper ordering of all of the transitive
 	// deps of this module
-	staticDepsInLinkOrder android.Paths
+	depsInLinkOrder android.Paths
+
+	// only non-nil when this is a shared library that reuses the objects of a static library
+	staticVariant *Module
 }
 
 func (c *Module) Init() android.Module {
@@ -537,7 +540,8 @@
 // orderDeps reorders dependencies into a list such that if module A depends on B, then
 // A will precede B in the resultant list.
 // This is convenient for passing into a linker.
-func orderDeps(directDeps []android.Path, transitiveDeps map[android.Path][]android.Path) (orderedAllDeps []android.Path, orderedDeclaredDeps []android.Path) {
+// Note that directSharedDeps should be the analogous static library for each shared lib dep
+func orderDeps(directStaticDeps []android.Path, directSharedDeps []android.Path, allTransitiveDeps map[android.Path][]android.Path) (orderedAllDeps []android.Path, orderedDeclaredDeps []android.Path) {
 	// If A depends on B, then
 	//   Every list containing A will also contain B later in the list
 	//   So, after concatenating all lists, the final instance of B will have come from the same
@@ -545,38 +549,46 @@
 	//   So, the final instance of B will be later in the concatenation than the final A
 	//   So, keeping only the final instance of A and of B ensures that A is earlier in the output
 	//     list than B
-	for _, dep := range directDeps {
+	for _, dep := range directStaticDeps {
 		orderedAllDeps = append(orderedAllDeps, dep)
-		orderedAllDeps = append(orderedAllDeps, transitiveDeps[dep]...)
+		orderedAllDeps = append(orderedAllDeps, allTransitiveDeps[dep]...)
+	}
+	for _, dep := range directSharedDeps {
+		orderedAllDeps = append(orderedAllDeps, dep)
+		orderedAllDeps = append(orderedAllDeps, allTransitiveDeps[dep]...)
 	}
 
 	orderedAllDeps = android.LastUniquePaths(orderedAllDeps)
 
-	// We don't want to add any new dependencies into directDeps (to allow the caller to
+	// We don't want to add any new dependencies into directStaticDeps (to allow the caller to
 	// intentionally exclude or replace any unwanted transitive dependencies), so we limit the
-	// resultant list to only what the caller has chosen to include in directDeps
-	_, orderedDeclaredDeps = android.FilterPathList(orderedAllDeps, directDeps)
+	// resultant list to only what the caller has chosen to include in directStaticDeps
+	_, orderedDeclaredDeps = android.FilterPathList(orderedAllDeps, directStaticDeps)
 
 	return orderedAllDeps, orderedDeclaredDeps
 }
 
-func orderStaticModuleDeps(module *Module, deps []*Module) (results []android.Path) {
-	// make map of transitive dependencies
-	transitiveStaticDepNames := make(map[android.Path][]android.Path, len(deps))
-	for _, dep := range deps {
-		transitiveStaticDepNames[dep.outputFile.Path()] = dep.staticDepsInLinkOrder
+func orderStaticModuleDeps(module *Module, staticDeps []*Module, sharedDeps []*Module) (results []android.Path) {
+	// convert Module to Path
+	allTransitiveDeps := make(map[android.Path][]android.Path, len(staticDeps))
+	staticDepFiles := []android.Path{}
+	for _, dep := range staticDeps {
+		allTransitiveDeps[dep.outputFile.Path()] = dep.depsInLinkOrder
+		staticDepFiles = append(staticDepFiles, dep.outputFile.Path())
 	}
-	// get the output file for each declared dependency
-	depFiles := []android.Path{}
-	for _, dep := range deps {
-		depFiles = append(depFiles, dep.outputFile.Path())
+	sharedDepFiles := []android.Path{}
+	for _, sharedDep := range sharedDeps {
+		staticAnalogue := sharedDep.staticVariant
+		if staticAnalogue != nil {
+			allTransitiveDeps[staticAnalogue.outputFile.Path()] = staticAnalogue.depsInLinkOrder
+			sharedDepFiles = append(sharedDepFiles, staticAnalogue.outputFile.Path())
+		}
 	}
 
 	// reorder the dependencies based on transitive dependencies
-	module.staticDepsInLinkOrder, results = orderDeps(depFiles, transitiveStaticDepNames)
+	module.depsInLinkOrder, results = orderDeps(staticDepFiles, sharedDepFiles, allTransitiveDeps)
 
 	return results
-
 }
 
 func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
@@ -1026,6 +1038,7 @@
 	var depPaths PathDeps
 
 	directStaticDeps := []*Module{}
+	directSharedDeps := []*Module{}
 
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		depName := ctx.OtherModuleName(dep)
@@ -1092,6 +1105,7 @@
 		// re-exporting flags
 		if depTag == reuseObjTag {
 			if l, ok := ccDep.compiler.(libraryInterface); ok {
+				c.staticVariant = ccDep
 				objs, flags, deps := l.reuseObjs()
 				depPaths.Objs = depPaths.Objs.Append(objs)
 				depPaths.ReexportedFlags = append(depPaths.ReexportedFlags, flags...)
@@ -1130,6 +1144,7 @@
 			ptr = &depPaths.SharedLibs
 			depPtr = &depPaths.SharedLibsDeps
 			depFile = ccDep.linker.(libraryInterface).toc()
+			directSharedDeps = append(directSharedDeps, ccDep)
 		case lateSharedDepTag, ndkLateStubDepTag:
 			ptr = &depPaths.LateSharedLibs
 			depPtr = &depPaths.LateSharedLibsDeps
@@ -1221,7 +1236,7 @@
 	})
 
 	// use the ordered dependencies as this module's dependencies
-	depPaths.StaticLibs = append(depPaths.StaticLibs, orderStaticModuleDeps(c, directStaticDeps)...)
+	depPaths.StaticLibs = append(depPaths.StaticLibs, orderStaticModuleDeps(c, directStaticDeps, directSharedDeps)...)
 
 	// Dedup exported flags from dependencies
 	depPaths.Flags = android.FirstUniqueStrings(depPaths.Flags)
diff --git a/cc/cc_test.go b/cc/cc_test.go
index bdc8c17..148b4dd 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -289,7 +289,11 @@
 var staticLinkDepOrderTestCases = []struct {
 	// This is a string representation of a map[moduleName][]moduleDependency .
 	// It models the dependencies declared in an Android.bp file.
-	in string
+	inStatic string
+
+	// This is a string representation of a map[moduleName][]moduleDependency .
+	// It models the dependencies declared in an Android.bp file.
+	inShared string
 
 	// allOrdered is a string representation of a map[moduleName][]moduleDependency .
 	// The keys of allOrdered specify which modules we would like to check.
@@ -305,82 +309,99 @@
 }{
 	// Simple tests
 	{
-		in:         "",
+		inStatic:   "",
 		outOrdered: "",
 	},
 	{
-		in:         "a:",
+		inStatic:   "a:",
 		outOrdered: "a:",
 	},
 	{
-		in:         "a:b; b:",
+		inStatic:   "a:b; b:",
 		outOrdered: "a:b; b:",
 	},
 	// Tests of reordering
 	{
 		// diamond example
-		in:         "a:d,b,c; b:d; c:d; d:",
+		inStatic:   "a:d,b,c; b:d; c:d; d:",
 		outOrdered: "a:b,c,d; b:d; c:d; d:",
 	},
 	{
 		// somewhat real example
-		in:         "bsdiff_unittest:b,c,d,e,f,g,h,i; e:b",
+		inStatic:   "bsdiff_unittest:b,c,d,e,f,g,h,i; e:b",
 		outOrdered: "bsdiff_unittest:c,d,e,b,f,g,h,i; e:b",
 	},
 	{
 		// multiple reorderings
-		in:         "a:b,c,d,e; d:b; e:c",
+		inStatic:   "a:b,c,d,e; d:b; e:c",
 		outOrdered: "a:d,b,e,c; d:b; e:c",
 	},
 	{
 		// should reorder without adding new transitive dependencies
-		in:         "bin:lib2,lib1;             lib1:lib2,liboptional",
+		inStatic:   "bin:lib2,lib1;             lib1:lib2,liboptional",
 		allOrdered: "bin:lib1,lib2,liboptional; lib1:lib2,liboptional",
 		outOrdered: "bin:lib1,lib2;             lib1:lib2,liboptional",
 	},
 	{
 		// multiple levels of dependencies
-		in:         "a:b,c,d,e,f,g,h; f:b,c,d; b:c,d; c:d",
+		inStatic:   "a:b,c,d,e,f,g,h; f:b,c,d; b:c,d; c:d",
 		allOrdered: "a:e,f,b,c,d,g,h; f:b,c,d; b:c,d; c:d",
 		outOrdered: "a:e,f,b,c,d,g,h; f:b,c,d; b:c,d; c:d",
 	},
+	// shared dependencies
+	{
+		// Note that this test doesn't recurse, to minimize the amount of logic it tests.
+		// So, we don't actually have to check that a shared dependency of c will change the order
+		// of a library that depends statically on b and on c.  We only need to check that if c has
+		// a shared dependency on b, that that shows up in allOrdered.
+		inShared:   "c:b",
+		allOrdered: "c:b",
+		outOrdered: "c:",
+	},
+	{
+		// This test doesn't actually include any shared dependencies but it's a reminder of what
+		// the second phase of the above test would look like
+		inStatic:   "a:b,c; c:b",
+		allOrdered: "a:c,b; c:b",
+		outOrdered: "a:c,b; c:b",
+	},
 	// tiebreakers for when two modules specifying different orderings and there is no dependency
 	// to dictate an order
 	{
 		// if the tie is between two modules at the end of a's deps, then a's order wins
-		in:         "a1:b,c,d,e; a2:b,c,e,d; b:d,e; c:e,d",
+		inStatic:   "a1:b,c,d,e; a2:b,c,e,d; b:d,e; c:e,d",
 		outOrdered: "a1:b,c,d,e; a2:b,c,e,d; b:d,e; c:e,d",
 	},
 	{
 		// if the tie is between two modules at the start of a's deps, then c's order is used
-		in:         "a1:d,e,b1,c1; b1:d,e; c1:e,d;   a2:d,e,b2,c2; b2:d,e; c2:d,e",
+		inStatic:   "a1:d,e,b1,c1; b1:d,e; c1:e,d;   a2:d,e,b2,c2; b2:d,e; c2:d,e",
 		outOrdered: "a1:b1,c1,e,d; b1:d,e; c1:e,d;   a2:b2,c2,d,e; b2:d,e; c2:d,e",
 	},
 	// Tests involving duplicate dependencies
 	{
 		// simple duplicate
-		in:         "a:b,c,c,b",
+		inStatic:   "a:b,c,c,b",
 		outOrdered: "a:c,b",
 	},
 	{
 		// duplicates with reordering
-		in:         "a:b,c,d,c; c:b",
+		inStatic:   "a:b,c,d,c; c:b",
 		outOrdered: "a:d,c,b",
 	},
 	// Tests to confirm the nonexistence of infinite loops.
 	// These cases should never happen, so as long as the test terminates and the
 	// result is deterministic then that should be fine.
 	{
-		in:         "a:a",
+		inStatic:   "a:a",
 		outOrdered: "a:a",
 	},
 	{
-		in:         "a:b;   b:c;   c:a",
+		inStatic:   "a:b;   b:c;   c:a",
 		allOrdered: "a:b,c; b:c,a; c:a,b",
 		outOrdered: "a:b;   b:c;   c:a",
 	},
 	{
-		in:         "a:b,c;   b:c,a;   c:a,b",
+		inStatic:   "a:b,c;   b:c,a;   c:a,b",
 		allOrdered: "a:c,a,b; b:a,b,c; c:b,c,a",
 		outOrdered: "a:c,b;   b:a,c;   c:b,a",
 	},
@@ -427,43 +448,47 @@
 	return modulesInOrder, allDeps
 }
 
-func TestStaticLinkDependencyOrdering(t *testing.T) {
+func TestLinkReordering(t *testing.T) {
 	for _, testCase := range staticLinkDepOrderTestCases {
 		errs := []string{}
 
 		// parse testcase
-		_, givenTransitiveDeps := parseModuleDeps(testCase.in)
+		_, givenTransitiveDeps := parseModuleDeps(testCase.inStatic)
 		expectedModuleNames, expectedTransitiveDeps := parseModuleDeps(testCase.outOrdered)
 		if testCase.allOrdered == "" {
 			// allow the test case to skip specifying allOrdered
 			testCase.allOrdered = testCase.outOrdered
 		}
 		_, expectedAllDeps := parseModuleDeps(testCase.allOrdered)
+		_, givenAllSharedDeps := parseModuleDeps(testCase.inShared)
 
 		// For each module whose post-reordered dependencies were specified, validate that
 		// reordering the inputs produces the expected outputs.
 		for _, moduleName := range expectedModuleNames {
 			moduleDeps := givenTransitiveDeps[moduleName]
-			orderedAllDeps, orderedDeclaredDeps := orderDeps(moduleDeps, givenTransitiveDeps)
+			givenSharedDeps := givenAllSharedDeps[moduleName]
+			orderedAllDeps, orderedDeclaredDeps := orderDeps(moduleDeps, givenSharedDeps, givenTransitiveDeps)
 
 			correctAllOrdered := expectedAllDeps[moduleName]
 			if !reflect.DeepEqual(orderedAllDeps, correctAllOrdered) {
 				errs = append(errs, fmt.Sprintf("orderDeps returned incorrect orderedAllDeps."+
-					"\nInput:    %q"+
+					"\nin static:%q"+
+					"\nin shared:%q"+
 					"\nmodule:   %v"+
 					"\nexpected: %s"+
 					"\nactual:   %s",
-					testCase.in, moduleName, correctAllOrdered, orderedAllDeps))
+					testCase.inStatic, testCase.inShared, moduleName, correctAllOrdered, orderedAllDeps))
 			}
 
 			correctOutputDeps := expectedTransitiveDeps[moduleName]
 			if !reflect.DeepEqual(correctOutputDeps, orderedDeclaredDeps) {
 				errs = append(errs, fmt.Sprintf("orderDeps returned incorrect orderedDeclaredDeps."+
-					"\nInput:    %q"+
+					"\nin static:%q"+
+					"\nin shared:%q"+
 					"\nmodule:   %v"+
 					"\nexpected: %s"+
 					"\nactual:   %s",
-					testCase.in, moduleName, correctOutputDeps, orderedDeclaredDeps))
+					testCase.inStatic, testCase.inShared, moduleName, correctOutputDeps, orderedDeclaredDeps))
 			}
 		}
 
@@ -493,7 +518,7 @@
 	return paths
 }
 
-func TestLibDeps(t *testing.T) {
+func TestStaticLibDepReordering(t *testing.T) {
 	ctx := testCc(t, `
 	cc_library {
 		name: "a",
@@ -514,7 +539,7 @@
 
 	variant := "android_arm64_armv8-a_core_static"
 	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
-	actual := moduleA.staticDepsInLinkOrder
+	actual := moduleA.depsInLinkOrder
 	expected := getOutputPaths(ctx, variant, []string{"c", "b", "d"})
 
 	if !reflect.DeepEqual(actual, expected) {
@@ -527,6 +552,37 @@
 	}
 }
 
+func TestStaticLibDepReorderingWithShared(t *testing.T) {
+	ctx := testCc(t, `
+	cc_library {
+		name: "a",
+		static_libs: ["b", "c"],
+	}
+	cc_library {
+		name: "b",
+	}
+	cc_library {
+		name: "c",
+		shared_libs: ["b"],
+	}
+
+	`)
+
+	variant := "android_arm64_armv8-a_core_static"
+	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
+	actual := moduleA.depsInLinkOrder
+	expected := getOutputPaths(ctx, variant, []string{"c", "b"})
+
+	if !reflect.DeepEqual(actual, expected) {
+		t.Errorf("staticDeps orderings did not account for shared libs"+
+			"\nactual:   %v"+
+			"\nexpected: %v",
+			actual,
+			expected,
+		)
+	}
+}
+
 var compilerFlagsTestCases = []struct {
 	in  string
 	out bool
diff --git a/cmd/merge_zips/merge_zips.go b/cmd/merge_zips/merge_zips.go
index 207b161..556dad0 100644
--- a/cmd/merge_zips/merge_zips.go
+++ b/cmd/merge_zips/merge_zips.go
@@ -54,13 +54,14 @@
 }
 
 var (
-	sortEntries     = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
-	emulateJar      = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
-	stripDirs       fileList
-	stripFiles      fileList
-	zipsToNotStrip  = make(zipsToNotStripSet)
-	stripDirEntries = flag.Bool("D", false, "strip directory entries from the output zip file")
-	manifest        = flag.String("m", "", "manifest file to insert in jar")
+	sortEntries      = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
+	emulateJar       = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
+	stripDirs        fileList
+	stripFiles       fileList
+	zipsToNotStrip   = make(zipsToNotStripSet)
+	stripDirEntries  = flag.Bool("D", false, "strip directory entries from the output zip file")
+	manifest         = flag.String("m", "", "manifest file to insert in jar")
+	ignoreDuplicates = flag.Bool("ignore-duplicates", false, "take each entry from the first zip it exists in and don't warn")
 )
 
 func init() {
@@ -118,7 +119,7 @@
 	}
 
 	// do merge
-	err = mergeZips(readers, writer, *manifest, *sortEntries, *emulateJar, *stripDirEntries)
+	err = mergeZips(readers, writer, *manifest, *sortEntries, *emulateJar, *stripDirEntries, *ignoreDuplicates)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -210,7 +211,7 @@
 }
 
 func mergeZips(readers []namedZipReader, writer *zip.Writer, manifest string,
-	sortEntries, emulateJar, stripDirEntries bool) error {
+	sortEntries, emulateJar, stripDirEntries, ignoreDuplicates bool) error {
 
 	sourceByDest := make(map[string]zipSource, 0)
 	orderedMappings := []fileMapping{}
@@ -266,6 +267,9 @@
 					return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
 						dest, existingSource, source)
 				}
+				if ignoreDuplicates {
+					continue
+				}
 				if emulateJar &&
 					file.Name == jar.ManifestFile || file.Name == jar.ModuleInfoClass {
 					// Skip manifest and module info files that are not from the first input file
diff --git a/cmd/zip2zip/Android.bp b/cmd/zip2zip/Android.bp
index 6420219..68d8bc7 100644
--- a/cmd/zip2zip/Android.bp
+++ b/cmd/zip2zip/Android.bp
@@ -15,8 +15,9 @@
 blueprint_go_binary {
     name: "zip2zip",
     deps: [
-      "android-archive-zip",
-      "soong-jar",
+        "android-archive-zip",
+        "blueprint-pathtools",
+        "soong-jar",
     ],
     srcs: [
         "zip2zip.go",
diff --git a/cmd/zip2zip/zip2zip.go b/cmd/zip2zip/zip2zip.go
index f48d458..e8ea9b9 100644
--- a/cmd/zip2zip/zip2zip.go
+++ b/cmd/zip2zip/zip2zip.go
@@ -24,6 +24,8 @@
 	"strings"
 	"time"
 
+	"github.com/google/blueprint/pathtools"
+
 	"android/soong/jar"
 	"android/soong/third_party/zip"
 )
@@ -36,8 +38,14 @@
 	setTime   = flag.Bool("t", false, "set timestamps to 2009-01-01 00:00:00")
 
 	staticTime = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC)
+
+	excludes excludeArgs
 )
 
+func init() {
+	flag.Var(&excludes, "x", "exclude a filespec from the output")
+}
+
 func main() {
 	flag.Usage = func() {
 		fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...")
@@ -45,15 +53,14 @@
 		fmt.Fprintln(os.Stderr, "  filespec:")
 		fmt.Fprintln(os.Stderr, "    <name>")
 		fmt.Fprintln(os.Stderr, "    <in_name>:<out_name>")
-		fmt.Fprintln(os.Stderr, "    <glob>:<out_dir>/")
+		fmt.Fprintln(os.Stderr, "    <glob>[:<out_dir>]")
 		fmt.Fprintln(os.Stderr, "")
-		fmt.Fprintln(os.Stderr, "<glob> uses the rules at https://golang.org/pkg/path/filepath/#Match")
-		fmt.Fprintln(os.Stderr, "As a special exception, '**' is supported to specify all files in the input zip.")
+		fmt.Fprintln(os.Stderr, "<glob> uses the rules at https://godoc.org/github.com/google/blueprint/pathtools/#Match")
 		fmt.Fprintln(os.Stderr, "")
 		fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to")
 		fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.")
 		fmt.Fprintln(os.Stderr, "")
-		fmt.Fprintln(os.Stderr, "If no filepsec is provided all files are copied (equivalent to '**').")
+		fmt.Fprintln(os.Stderr, "If no filepsec is provided all files and directories are copied.")
 	}
 
 	flag.Parse()
@@ -85,7 +92,9 @@
 		}
 	}()
 
-	if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime, flag.Args()); err != nil {
+	if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime,
+		flag.Args(), excludes); err != nil {
+
 		log.Fatal(err)
 	}
 }
@@ -95,91 +104,126 @@
 	newName string
 }
 
-func zip2zip(reader *zip.Reader, writer *zip.Writer, sortGlobs, sortJava, setTime bool, args []string) error {
-	if len(args) == 0 {
-		// If no filespec is provided, default to copying everything
-		args = []string{"**"}
-	}
-	for _, arg := range args {
-		var input string
-		var output string
+func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool,
+	includes []string, excludes []string) error {
 
+	matches := []pair{}
+
+	sortMatches := func(matches []pair) {
+		if sortJava {
+			sort.SliceStable(matches, func(i, j int) bool {
+				return jar.EntryNamesLess(matches[i].newName, matches[j].newName)
+			})
+		} else if sortOutput {
+			sort.SliceStable(matches, func(i, j int) bool {
+				return matches[i].newName < matches[j].newName
+			})
+		}
+	}
+
+	for _, include := range includes {
 		// Reserve escaping for future implementation, so make sure no
 		// one is using \ and expecting a certain behavior.
-		if strings.Contains(arg, "\\") {
+		if strings.Contains(include, "\\") {
 			return fmt.Errorf("\\ characters are not currently supported")
 		}
 
-		args := strings.SplitN(arg, ":", 2)
-		input = args[0]
-		if len(args) == 2 {
-			output = args[1]
-		}
+		input, output := includeSplit(include)
 
-		matches := []pair{}
-		if strings.IndexAny(input, "*?[") >= 0 {
-			matchAll := input == "**"
-			if !matchAll && strings.Contains(input, "**") {
-				return fmt.Errorf("** is only supported on its own, not with other characters")
-			}
+		var includeMatches []pair
 
-			for _, file := range reader.File {
-				match := matchAll
-
-				if !match {
-					var err error
-					match, err = filepath.Match(input, file.Name)
-					if err != nil {
-						return err
-					}
-				}
-
-				if match {
-					var newName string
-					if output == "" {
-						newName = file.Name
-					} else {
+		for _, file := range reader.File {
+			var newName string
+			if match, err := pathtools.Match(input, file.Name); err != nil {
+				return err
+			} else if match {
+				if output == "" {
+					newName = file.Name
+				} else {
+					if pathtools.IsGlob(input) {
+						// If the input is a glob then the output is a directory.
 						_, name := filepath.Split(file.Name)
 						newName = filepath.Join(output, name)
+					} else {
+						// Otherwise it is a file.
+						newName = output
 					}
-					matches = append(matches, pair{file, newName})
 				}
-			}
-
-			if sortJava {
-				jarSort(matches)
-			} else if sortGlobs {
-				sort.SliceStable(matches, func(i, j int) bool {
-					return matches[i].newName < matches[j].newName
-				})
-			}
-		} else {
-			if output == "" {
-				output = input
-			}
-			for _, file := range reader.File {
-				if input == file.Name {
-					matches = append(matches, pair{file, output})
-					break
-				}
+				includeMatches = append(includeMatches, pair{file, newName})
 			}
 		}
 
-		for _, match := range matches {
-			if setTime {
-				match.File.SetModTime(staticTime)
-			}
-			if err := writer.CopyFrom(match.File, match.newName); err != nil {
+		sortMatches(includeMatches)
+		matches = append(matches, includeMatches...)
+	}
+
+	if len(includes) == 0 {
+		// implicitly match everything
+		for _, file := range reader.File {
+			matches = append(matches, pair{file, file.Name})
+		}
+		sortMatches(matches)
+	}
+
+	var matchesAfterExcludes []pair
+	seen := make(map[string]*zip.File)
+
+	for _, match := range matches {
+		// Filter out matches whose original file name matches an exclude filter
+		excluded := false
+		for _, exclude := range excludes {
+			if excludeMatch, err := pathtools.Match(exclude, match.File.Name); err != nil {
 				return err
+			} else if excludeMatch {
+				excluded = true
+				break
 			}
 		}
+
+		if excluded {
+			continue
+		}
+
+		// Check for duplicate output names, ignoring ones that come from the same input zip entry.
+		if prev, exists := seen[match.newName]; exists {
+			if prev != match.File {
+				return fmt.Errorf("multiple entries for %q with different contents", match.newName)
+			}
+			continue
+		}
+		seen[match.newName] = match.File
+
+		matchesAfterExcludes = append(matchesAfterExcludes, match)
+	}
+
+	for _, match := range matchesAfterExcludes {
+		if setTime {
+			match.File.SetModTime(staticTime)
+		}
+		if err := writer.CopyFrom(match.File, match.newName); err != nil {
+			return err
+		}
 	}
 
 	return nil
 }
 
-func jarSort(files []pair) {
-	sort.SliceStable(files, func(i, j int) bool {
-		return jar.EntryNamesLess(files[i].newName, files[j].newName)
-	})
+func includeSplit(s string) (string, string) {
+	split := strings.SplitN(s, ":", 2)
+	if len(split) == 2 {
+		return split[0], split[1]
+	} else {
+		return split[0], ""
+	}
+}
+
+type excludeArgs []string
+
+func (e *excludeArgs) String() string {
+	return strings.Join(*e, " ")
+}
+
+func (e *excludeArgs) Set(s string) error {
+	*e = append(*e, s)
+	return nil
 }
diff --git a/cmd/zip2zip/zip2zip_test.go b/cmd/zip2zip/zip2zip_test.go
index 53c8ce2..212ab28 100644
--- a/cmd/zip2zip/zip2zip_test.go
+++ b/cmd/zip2zip/zip2zip_test.go
@@ -30,6 +30,7 @@
 	sortGlobs  bool
 	sortJava   bool
 	args       []string
+	excludes   []string
 
 	outputFiles []string
 	err         error
@@ -41,13 +42,6 @@
 
 		err: fmt.Errorf("\\ characters are not currently supported"),
 	},
-	{
-		name: "unsupported **",
-
-		args: []string{"a/**:b"},
-
-		err: fmt.Errorf("** is only supported on its own, not with other characters"),
-	},
 	{ // This is modelled after the update package build rules in build/make/core/Makefile
 		name: "filter globs",
 
@@ -95,16 +89,19 @@
 		name: "sort all",
 
 		inputFiles: []string{
+			"RADIO/",
 			"RADIO/a",
+			"IMAGES/",
 			"IMAGES/system.img",
 			"IMAGES/b.txt",
 			"IMAGES/recovery.img",
 			"IMAGES/vendor.img",
+			"OTA/",
 			"OTA/b",
 			"OTA/android-info.txt",
 		},
 		sortGlobs: true,
-		args:      []string{"**"},
+		args:      []string{"**/*"},
 
 		outputFiles: []string{
 			"IMAGES/b.txt",
@@ -120,11 +117,14 @@
 		name: "sort all implicit",
 
 		inputFiles: []string{
+			"RADIO/",
 			"RADIO/a",
+			"IMAGES/",
 			"IMAGES/system.img",
 			"IMAGES/b.txt",
 			"IMAGES/recovery.img",
 			"IMAGES/vendor.img",
+			"OTA/",
 			"OTA/b",
 			"OTA/android-info.txt",
 		},
@@ -132,12 +132,15 @@
 		args:      nil,
 
 		outputFiles: []string{
+			"IMAGES/",
 			"IMAGES/b.txt",
 			"IMAGES/recovery.img",
 			"IMAGES/system.img",
 			"IMAGES/vendor.img",
+			"OTA/",
 			"OTA/android-info.txt",
 			"OTA/b",
+			"RADIO/",
 			"RADIO/a",
 		},
 	},
@@ -177,7 +180,7 @@
 			"b",
 			"a",
 		},
-		args: []string{"a:a2", "**"},
+		args: []string{"a:a2", "**/*"},
 
 		outputFiles: []string{
 			"a2",
@@ -185,6 +188,69 @@
 			"a",
 		},
 	},
+	{
+		name: "multiple matches",
+
+		inputFiles: []string{
+			"a/a",
+		},
+		args: []string{"a/a", "a/*"},
+
+		outputFiles: []string{
+			"a/a",
+		},
+	},
+	{
+		name: "multiple conflicting matches",
+
+		inputFiles: []string{
+			"a/a",
+			"a/b",
+		},
+		args: []string{"a/b:a/a", "a/*"},
+
+		err: fmt.Errorf(`multiple entries for "a/a" with different contents`),
+	},
+	{
+		name: "excludes",
+
+		inputFiles: []string{
+			"a/a",
+			"a/b",
+		},
+		args:     nil,
+		excludes: []string{"a/a"},
+
+		outputFiles: []string{
+			"a/b",
+		},
+	},
+	{
+		name: "excludes with include",
+
+		inputFiles: []string{
+			"a/a",
+			"a/b",
+		},
+		args:     []string{"a/*"},
+		excludes: []string{"a/a"},
+
+		outputFiles: []string{
+			"a/b",
+		},
+	},
+	{
+		name: "excludes with glob",
+
+		inputFiles: []string{
+			"a/a",
+			"a/b",
+		},
+		args:     []string{"a/*"},
+		excludes: []string{"a/*"},
+
+		outputFiles: nil,
+	},
 }
 
 func errorString(e error) string {
@@ -216,7 +282,7 @@
 			}
 
 			outputWriter := zip.NewWriter(outputBuf)
-			err = zip2zip(inputReader, outputWriter, testCase.sortGlobs, testCase.sortJava, false, testCase.args)
+			err = zip2zip(inputReader, outputWriter, testCase.sortGlobs, testCase.sortJava, false, testCase.args, testCase.excludes)
 			if errorString(testCase.err) != errorString(err) {
 				t.Fatalf("Unexpected error:\n got: %q\nwant: %q", errorString(err), errorString(testCase.err))
 			}
diff --git a/java/androidmk.go b/java/androidmk.go
index 1c0526a..df83faa 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -36,12 +36,16 @@
 				}
 				if library.dexJarFile != nil {
 					fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", library.dexJarFile.String())
-					if library.deviceProperties.Dex_preopt == nil || *library.deviceProperties.Dex_preopt == false {
+					if library.deviceProperties.Dex_preopt != nil && *library.deviceProperties.Dex_preopt == false {
 						fmt.Fprintln(w, "LOCAL_DEX_PREOPT := false")
 					}
 				}
 				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", String(library.deviceProperties.Sdk_version))
 				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String())
+
+				if library.jacocoReportClassesFile != nil {
+					fmt.Fprintln(w, "LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR :=", library.jacocoReportClassesFile.String())
+				}
 			},
 		},
 		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
diff --git a/java/app.go b/java/app.go
index 05cc975..68e4d5c 100644
--- a/java/app.go
+++ b/java/app.go
@@ -54,6 +54,8 @@
 	// list of directories relative to the Blueprints file containing
 	// Android resources
 	Resource_dirs []string
+
+	Instrumentation_for *string
 }
 
 type AndroidApp struct {
@@ -119,6 +121,10 @@
 	//	a.properties.Proguard.Enabled = true
 	//}
 
+	if String(a.appProperties.Instrumentation_for) == "" {
+		a.properties.Instrument = true
+	}
+
 	a.Module.compile(ctx)
 
 	aaptPackageFlags := append([]string(nil), aaptFlags...)
diff --git a/java/config/config.go b/java/config/config.go
index 3cd2841..49481be 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -28,6 +28,16 @@
 	DefaultBootclasspathLibraries = []string{"core-oj", "core-libart"}
 	DefaultSystemModules          = "core-system-modules"
 	DefaultLibraries              = []string{"ext", "framework", "okhttp"}
+
+	DefaultJacocoExcludeFilter = []string{"org.junit.*", "org.jacoco.*", "org.mockito.*"}
+
+	InstrumentFrameworkModules = []string{
+		"framework",
+		"telephony-common",
+		"services",
+		"android.car",
+		"android.car7",
+	}
 )
 
 func init() {
@@ -74,6 +84,7 @@
 	pctx.SourcePathVariable("JarArgsCmd", "build/soong/scripts/jar-args.sh")
 	pctx.HostBinToolVariable("SoongZipCmd", "soong_zip")
 	pctx.HostBinToolVariable("MergeZipsCmd", "merge_zips")
+	pctx.HostBinToolVariable("Zip2ZipCmd", "zip2zip")
 	pctx.VariableFunc("DxCmd", func(config interface{}) (string, error) {
 		if config.(android.Config).IsEnvFalse("USE_D8") {
 			if config.(android.Config).UnbundledBuild() || config.(android.Config).IsPdkBuild() {
@@ -117,4 +128,6 @@
 		}
 		return "", nil
 	})
+
+	pctx.HostJavaToolVariable("JacocoCLIJar", "jacoco-cli.jar")
 }
diff --git a/java/config/makevars.go b/java/config/makevars.go
index dabf2e7..b9009f3 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -62,4 +62,7 @@
 
 	ctx.Strict("SOONG_JAVAC_WRAPPER", "${SoongJavacWrapper}")
 	ctx.Strict("EXTRACT_SRCJARS", "${ExtractSrcJarsCmd}")
+
+	ctx.Strict("JACOCO_CLI_JAR", "${JacocoCLIJar}")
+	ctx.Strict("DEFAULT_JACOCO_EXCLUDE_FILTER", strings.Join(DefaultJacocoExcludeFilter, ","))
 }
diff --git a/java/jacoco.go b/java/jacoco.go
new file mode 100644
index 0000000..b26b046
--- /dev/null
+++ b/java/jacoco.go
@@ -0,0 +1,108 @@
+// Copyright 2017 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
+
+// Rules for instrumenting classes using jacoco
+
+import (
+	"strings"
+
+	"github.com/google/blueprint"
+
+	"android/soong/android"
+)
+
+var (
+	jacoco = pctx.AndroidStaticRule("jacoco", blueprint.RuleParams{
+		Command: `${config.Zip2ZipCmd} -i $in -o $strippedJar $stripSpec && ` +
+			`${config.JavaCmd} -jar ${config.JacocoCLIJar} instrument -quiet -dest $instrumentedJar $strippedJar && ` +
+			`${config.Ziptime} $instrumentedJar && ` +
+			`${config.MergeZipsCmd} --ignore-duplicates -j $out $instrumentedJar $in`,
+		CommandDeps: []string{
+			"${config.Zip2ZipCmd}",
+			"${config.JavaCmd}",
+			"${config.JacocoCLIJar}",
+			"${config.Ziptime}",
+			"${config.MergeZipsCmd}",
+		},
+	},
+		"strippedJar", "stripSpec", "instrumentedJar")
+)
+
+func jacocoInstrumentJar(ctx android.ModuleContext, outputJar, strippedJar android.WritablePath,
+	inputJar android.Path, stripSpec string) {
+	instrumentedJar := android.PathForModuleOut(ctx, "jacoco/instrumented.jar")
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:           jacoco,
+		Description:    "jacoco",
+		Output:         outputJar,
+		ImplicitOutput: strippedJar,
+		Input:          inputJar,
+		Args: map[string]string{
+			"strippedJar":     strippedJar.String(),
+			"stripSpec":       stripSpec,
+			"instrumentedJar": instrumentedJar.String(),
+		},
+	})
+}
+
+func (j *Module) jacocoStripSpecs(ctx android.ModuleContext) string {
+	includes := jacocoFiltersToSpecs(ctx,
+		j.properties.Jacoco.Include_filter, "jacoco.include_filter")
+	excludes := jacocoFiltersToSpecs(ctx,
+		j.properties.Jacoco.Exclude_filter, "jacoco.exclude_filter")
+
+	specs := ""
+	if len(excludes) > 0 {
+		specs += android.JoinWithPrefix(excludes, "-x") + " "
+	}
+
+	if len(includes) > 0 {
+		specs += strings.Join(includes, " ")
+	} else {
+		specs += "**/*.class"
+	}
+
+	return specs
+}
+
+func jacocoFiltersToSpecs(ctx android.ModuleContext, filters []string, property string) []string {
+	specs := make([]string, len(filters))
+	for i, f := range filters {
+		specs[i] = jacocoFilterToSpec(ctx, f, property)
+	}
+	return specs
+}
+
+func jacocoFilterToSpec(ctx android.ModuleContext, filter string, property string) string {
+	wildcard := strings.HasSuffix(filter, "*")
+	filter = strings.TrimSuffix(filter, "*")
+	recursiveWildcard := wildcard && (strings.HasSuffix(filter, ".") || filter == "")
+
+	if strings.ContainsRune(filter, '*') {
+		ctx.PropertyErrorf(property, "'*' is only supported as the last character in a filter")
+	}
+
+	spec := strings.Replace(filter, ".", "/", -1)
+
+	if recursiveWildcard {
+		spec += "**/*.class"
+	} else if wildcard {
+		spec += "*.class"
+	}
+
+	return spec
+}
diff --git a/java/java.go b/java/java.go
index b2bd2b0..417cf74 100644
--- a/java/java.go
+++ b/java/java.go
@@ -51,9 +51,6 @@
 //  Renderscript
 // Post-jar passes:
 //  Proguard
-//  Jacoco
-//  Jarjar
-//  Dex
 // Rmtypedefs
 // DroidDoc
 // Findbugs
@@ -127,6 +124,26 @@
 		// List of javac flags that should only be used when passing -source 1.9
 		Javacflags []string
 	}
+
+	Jacoco struct {
+		// List of classes to include for instrumentation with jacoco to collect coverage
+		// information at runtime when building with coverage enabled.  If unset defaults to all
+		// classes.
+		// Supports '*' as the last character of an entry in the list as a wildcard match.
+		// If preceded by '.' it matches all classes in the package and subpackages, otherwise
+		// it matches classes in the package that have the class name as a prefix.
+		Include_filter []string
+
+		// List of classes to exclude from instrumentation with jacoco to collect coverage
+		// information at runtime when building with coverage enabled.  Overrides classes selected
+		// by the include_filter property.
+		// Supports '*' as the last character of an entry in the list as a wildcard match.
+		// If preceded by '.' it matches all classes in the package and subpackages, otherwise
+		// it matches classes in the package that have the class name as a prefix.
+		Exclude_filter []string
+	}
+
+	Instrument bool `blueprint:"mutated"`
 }
 
 type CompilerDeviceProperties struct {
@@ -177,6 +194,9 @@
 	// output file containing classes.dex
 	dexJarFile android.Path
 
+	// output file containing uninstrumented classes that will be instrumented by jacoco
+	jacocoReportClassesFile android.Path
+
 	// output file suitable for installing or running
 	outputFile android.Path
 
@@ -718,6 +738,20 @@
 	}
 
 	if ctx.Device() && j.installable() {
+		outputFile = j.desugar(ctx, flags, outputFile, jarName)
+	}
+
+	if ctx.AConfig().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
+		if inList(ctx.ModuleName(), config.InstrumentFrameworkModules) {
+			j.properties.Instrument = true
+		}
+	}
+
+	if ctx.AConfig().IsEnvTrue("EMMA_INSTRUMENT") && j.properties.Instrument {
+		outputFile = j.instrument(ctx, flags, outputFile, jarName)
+	}
+
+	if ctx.Device() && j.installable() {
 		outputFile = j.compileDex(ctx, flags, outputFile, jarName)
 		if ctx.Failed() {
 			return
@@ -766,6 +800,42 @@
 	return headerJar
 }
 
+func (j *Module) desugar(ctx android.ModuleContext, flags javaBuilderFlags,
+	classesJar android.Path, jarName string) android.Path {
+
+	desugarFlags := []string{
+		"--min_sdk_version " + j.minSdkVersionNumber(ctx),
+		"--desugar_try_with_resources_if_needed=false",
+		"--allow_empty_bootclasspath",
+	}
+
+	if inList("--core-library", j.deviceProperties.Dxflags) {
+		desugarFlags = append(desugarFlags, "--core_library")
+	}
+
+	flags.desugarFlags = strings.Join(desugarFlags, " ")
+
+	desugarJar := android.PathForModuleOut(ctx, "desugar", jarName)
+	TransformDesugar(ctx, desugarJar, classesJar, flags)
+
+	return desugarJar
+}
+
+func (j *Module) instrument(ctx android.ModuleContext, flags javaBuilderFlags,
+	classesJar android.Path, jarName string) android.Path {
+
+	specs := j.jacocoStripSpecs(ctx)
+
+	jacocoReportClassesFile := android.PathForModuleOut(ctx, "jacoco", "jacoco-report-classes.jar")
+	instrumentedJar := android.PathForModuleOut(ctx, "jacoco", jarName)
+
+	jacocoInstrumentJar(ctx, instrumentedJar, jacocoReportClassesFile, classesJar, specs)
+
+	j.jacocoReportClassesFile = jacocoReportClassesFile
+
+	return instrumentedJar
+}
+
 func (j *Module) compileDex(ctx android.ModuleContext, flags javaBuilderFlags,
 	classesJar android.Path, jarName string) android.Path {
 
@@ -792,47 +862,29 @@
 			"--dump-width=1000")
 	}
 
-	var minSdkVersion string
-	switch String(j.deviceProperties.Sdk_version) {
-	case "", "current", "test_current", "system_current":
-		minSdkVersion = strconv.Itoa(ctx.AConfig().DefaultAppTargetSdkInt())
-	default:
-		minSdkVersion = String(j.deviceProperties.Sdk_version)
-	}
-
-	dxFlags = append(dxFlags, "--min-sdk-version="+minSdkVersion)
+	dxFlags = append(dxFlags, "--min-sdk-version="+j.minSdkVersionNumber(ctx))
 
 	flags.dxFlags = strings.Join(dxFlags, " ")
 
-	desugarFlags := []string{
-		"--min_sdk_version " + minSdkVersion,
-		"--desugar_try_with_resources_if_needed=false",
-		"--allow_empty_bootclasspath",
-	}
-
-	if inList("--core-library", dxFlags) {
-		desugarFlags = append(desugarFlags, "--core_library")
-	}
-
-	flags.desugarFlags = strings.Join(desugarFlags, " ")
-
-	desugarJar := android.PathForModuleOut(ctx, "desugar", jarName)
-	TransformDesugar(ctx, desugarJar, classesJar, flags)
-	if ctx.Failed() {
-		return nil
-	}
-
 	// Compile classes.jar into classes.dex and then javalib.jar
 	javalibJar := android.PathForModuleOut(ctx, "dex", jarName)
-	TransformClassesJarToDexJar(ctx, javalibJar, desugarJar, flags)
-	if ctx.Failed() {
-		return nil
-	}
+	TransformClassesJarToDexJar(ctx, javalibJar, classesJar, flags)
 
 	j.dexJarFile = javalibJar
 	return javalibJar
 }
 
+// Returns a sdk version as a string that is guaranteed to be a parseable as a number.  For
+// modules targeting an unreleased SDK (meaning it does not yet have a number) it returns "10000".
+func (j *Module) minSdkVersionNumber(ctx android.ModuleContext) string {
+	switch String(j.deviceProperties.Sdk_version) {
+	case "", "current", "test_current", "system_current":
+		return strconv.Itoa(ctx.AConfig().DefaultAppTargetSdkInt())
+	default:
+		return String(j.deviceProperties.Sdk_version)
+	}
+}
+
 func (j *Module) installable() bool {
 	return j.properties.Installable == nil || *j.properties.Installable
 }
diff --git a/java/java_test.go b/java/java_test.go
index e1c90ee..143e82c 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -52,10 +52,11 @@
 	os.Exit(run())
 }
 func testJava(t *testing.T, bp string) *android.TestContext {
-	return testJavaWithEnv(t, bp, nil)
+	return testJavaWithEnvFs(t, bp, nil, nil)
 }
 
-func testJavaWithEnv(t *testing.T, bp string, env map[string]string) *android.TestContext {
+func testJavaWithEnvFs(t *testing.T, bp string,
+	env map[string]string, fs map[string][]byte) *android.TestContext {
 	config := android.TestArchConfig(buildDir, env)
 
 	ctx := android.NewTestArchContext()
@@ -112,17 +113,17 @@
 		}
 	}
 
-	ctx.MockFileSystem(map[string][]byte{
-		"Android.bp": []byte(bp),
-		"a.java":     nil,
-		"b.java":     nil,
-		"c.java":     nil,
-		"b.kt":       nil,
-		"a.jar":      nil,
-		"b.jar":      nil,
-		"res/a":      nil,
-		"res/b":      nil,
-		"res2/a":     nil,
+	mockFS := map[string][]byte{
+		"Android.bp":  []byte(bp),
+		"a.java":      nil,
+		"b.java":      nil,
+		"c.java":      nil,
+		"b.kt":        nil,
+		"a.jar":       nil,
+		"b.jar":       nil,
+		"java-res/a":  nil,
+		"java-res/b":  nil,
+		"java-res2/a": nil,
 
 		"prebuilts/sdk/14/android.jar":                nil,
 		"prebuilts/sdk/14/framework.aidl":             nil,
@@ -132,7 +133,13 @@
 		"prebuilts/sdk/system_current/framework.aidl": nil,
 		"prebuilts/sdk/test_current/android.jar":      nil,
 		"prebuilts/sdk/test_current/framework.aidl":   nil,
-	})
+	}
+
+	for k, v := range fs {
+		mockFS[k] = v
+	}
+
+	ctx.MockFileSystem(mockFS)
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	fail(t, errs)
@@ -394,7 +401,7 @@
 
 			// Test again with javac 1.9
 			t.Run("1.9", func(t *testing.T) {
-				ctx := testJavaWithEnv(t, bp, map[string]string{"EXPERIMENTAL_USE_OPENJDK9": "true"})
+				ctx := testJavaWithEnvFs(t, bp, map[string]string{"EXPERIMENTAL_USE_OPENJDK9": "true"}, nil)
 
 				javac := ctx.ModuleForTests("foo", variant).Rule("javac")
 				got := javac.Args["bootClasspath"]
@@ -497,14 +504,14 @@
 		{
 			// Test that a module with java_resource_dirs includes the files
 			name: "resource dirs",
-			prop: `java_resource_dirs: ["res"]`,
-			args: "-C res -f res/a -f res/b",
+			prop: `java_resource_dirs: ["java-res"]`,
+			args: "-C java-res -f java-res/a -f java-res/b",
 		},
 		{
 			// Test that a module with java_resources includes the files
 			name: "resource files",
-			prop: `java_resources: ["res/a", "res/b"]`,
-			args: "-C . -f res/a -f res/b",
+			prop: `java_resources: ["java-res/a", "java-res/b"]`,
+			args: "-C . -f java-res/a -f java-res/b",
 		},
 		{
 			// Test that a module with a filegroup in java_resources includes the files with the
@@ -514,10 +521,10 @@
 			extra: `
 				filegroup {
 					name: "foo-res",
-					path: "res",
-					srcs: ["res/a", "res/b"],
+					path: "java-res",
+					srcs: ["java-res/a", "java-res/b"],
 				}`,
-			args: "-C res -f res/a -f res/b",
+			args: "-C java-res -f java-res/a -f java-res/b",
 		},
 		{
 			// Test that a module with "include_srcs: true" includes its source files in the resources jar
@@ -562,21 +569,21 @@
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
-			java_resource_dirs: ["res", "res2"],
-			exclude_java_resource_dirs: ["res2"],
+			java_resource_dirs: ["java-res", "java-res2"],
+			exclude_java_resource_dirs: ["java-res2"],
 		}
 
 		java_library {
 			name: "bar",
 			srcs: ["a.java"],
-			java_resources: ["res/*"],
-			exclude_java_resources: ["res/b"],
+			java_resources: ["java-res/*"],
+			exclude_java_resources: ["java-res/b"],
 		}
 	`)
 
 	fooRes := ctx.ModuleForTests("foo", "android_common").Output("res/foo.jar")
 
-	expected := "-C res -f res/a -f res/b"
+	expected := "-C java-res -f java-res/a -f java-res/b"
 	if fooRes.Args["jarArgs"] != expected {
 		t.Errorf("foo resource jar args %q is not %q",
 			fooRes.Args["jarArgs"], expected)
@@ -585,7 +592,7 @@
 
 	barRes := ctx.ModuleForTests("bar", "android_common").Output("res/bar.jar")
 
-	expected = "-C . -f res/a"
+	expected = "-C . -f java-res/a"
 	if barRes.Args["jarArgs"] != expected {
 		t.Errorf("bar resource jar args %q is not %q",
 			barRes.Args["jarArgs"], expected)
@@ -606,7 +613,7 @@
 
 		genrule {
 			name: "gen",
-			tool_files: ["res/a"],
+			tool_files: ["java-res/a"],
 			out: ["gen.java"],
 		}
 	`)