Merge "Make ANDROID_JAVA{8,9}_HOME available to config.mk."
diff --git a/android/androidmk.go b/android/androidmk.go
index fb3934f..704b560 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -230,9 +230,15 @@
 		if Bool(amod.commonProperties.Proprietary) {
 			fmt.Fprintln(&data.preamble, "LOCAL_PROPRIETARY_MODULE := true")
 		}
-		if Bool(amod.commonProperties.Vendor) {
+		if Bool(amod.commonProperties.Vendor) || Bool(amod.commonProperties.Soc_specific) {
 			fmt.Fprintln(&data.preamble, "LOCAL_VENDOR_MODULE := true")
 		}
+		if Bool(amod.commonProperties.Device_specific) {
+			fmt.Fprintln(&data.preamble, "LOCAL_ODM_MODULE := true")
+		}
+		if Bool(amod.commonProperties.Product_specific) {
+			fmt.Fprintln(&data.preamble, "LOCAL_OEM_MODULE := true")
+		}
 		if amod.commonProperties.Owner != nil {
 			fmt.Fprintln(&data.preamble, "LOCAL_MODULE_OWNER :=", *amod.commonProperties.Owner)
 		}
diff --git a/android/arch.go b/android/arch.go
index af3919c..3a22569 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -971,7 +971,7 @@
 
 func getNdkAbisConfig() []archConfig {
 	return []archConfig{
-		{"arm", "armv5te", "", []string{"armeabi"}},
+		{"arm", "armv7-a", "", []string{"armeabi"}},
 		{"arm64", "armv8-a", "", []string{"arm64-v8a"}},
 		{"x86", "", "", []string{"x86"}},
 		{"x86_64", "", "", []string{"x86_64"}},
diff --git a/android/config.go b/android/config.go
index dd0301e..887291d 100644
--- a/android/config.go
+++ b/android/config.go
@@ -661,6 +661,20 @@
 	return c.config.ProductVariables.ExtraVndkVersions
 }
 
+func (c *deviceConfig) OdmPath() string {
+	if c.config.ProductVariables.OdmPath != nil {
+		return *c.config.ProductVariables.OdmPath
+	}
+	return "odm"
+}
+
+func (c *deviceConfig) OemPath() string {
+	if c.config.ProductVariables.OemPath != nil {
+		return *c.config.ProductVariables.OemPath
+	}
+	return "oem"
+}
+
 func (c *deviceConfig) BtConfigIncludeDir() string {
 	return String(c.config.ProductVariables.BtConfigIncludeDir)
 }
diff --git a/android/module.go b/android/module.go
index 3d8f683..cb068ab 100644
--- a/android/module.go
+++ b/android/module.go
@@ -65,7 +65,10 @@
 	Windows() bool
 	Debug() bool
 	PrimaryArch() bool
-	InstallOnVendorPartition() bool
+	Platform() bool
+	DeviceSpecific() bool
+	SocSpecific() bool
+	ProductSpecific() bool
 	AConfig() Config
 	DeviceConfig() DeviceConfig
 }
@@ -212,9 +215,26 @@
 	// vendor who owns this module
 	Owner *string
 
-	// whether this module is device specific and should be installed into /vendor
+	// whether this module is specific to an SoC (System-On-a-Chip). When set to true,
+	// it is installed into /vendor (or /system/vendor if vendor partition does not exist).
+	// Use `soc_specific` instead for better meaning.
 	Vendor *bool
 
+	// whether this module is specific to an SoC (System-On-a-Chip). When set to true,
+	// it is installed into /vendor (or /system/vendor if vendor partition does not exist).
+	Soc_specific *bool
+
+	// whether this module is specific to a device, not only for SoC, but also for off-chip
+	// peripherals. When set to true, it is installed into /odm (or /vendor/odm if odm partition
+	// does not exist, or /system/vendor/odm if both odm and vendor partitions do not exist).
+	// This implies `soc_specific:true`.
+	Device_specific *bool
+
+	// whether this module is specific to a software configuration of a product (e.g. country,
+	// network operator, etc). When set to true, it is installed into /oem (or /system/oem if
+	// oem partition does not exist).
+	Product_specific *bool
+
 	// init.rc files to be installed if this module is installed
 	Init_rc []string
 
@@ -264,6 +284,30 @@
 	NeitherHostNorDeviceSupported
 )
 
+type moduleKind int
+
+const (
+	platformModule moduleKind = iota
+	deviceSpecificModule
+	socSpecificModule
+	productSpecificModule
+)
+
+func (k moduleKind) String() string {
+	switch k {
+	case platformModule:
+		return "platform"
+	case deviceSpecificModule:
+		return "device-specific"
+	case socSpecificModule:
+		return "soc-specific"
+	case productSpecificModule:
+		return "product-specific"
+	default:
+		panic(fmt.Errorf("unknown module kind %d", k))
+	}
+}
+
 func InitAndroidModule(m Module) {
 	base := m.base()
 	base.module = m
@@ -546,11 +590,48 @@
 	}
 }
 
+func determineModuleKind(a *ModuleBase, ctx blueprint.BaseModuleContext) moduleKind {
+	var socSpecific = Bool(a.commonProperties.Vendor) || Bool(a.commonProperties.Proprietary) || Bool(a.commonProperties.Soc_specific)
+	var deviceSpecific = Bool(a.commonProperties.Device_specific)
+	var productSpecific = Bool(a.commonProperties.Product_specific)
+
+	if ((socSpecific || deviceSpecific) && productSpecific) || (socSpecific && deviceSpecific) {
+		msg := "conflicting value set here"
+		if productSpecific {
+			ctx.PropertyErrorf("product_specific", "a module cannot be specific to SoC or device and product at the same time.")
+			if deviceSpecific {
+				ctx.PropertyErrorf("device_specific", msg)
+			}
+		} else {
+			ctx.PropertyErrorf("device_specific", "a module cannot be specific to SoC and device at the same time.")
+		}
+		if Bool(a.commonProperties.Vendor) {
+			ctx.PropertyErrorf("vendor", msg)
+		}
+		if Bool(a.commonProperties.Proprietary) {
+			ctx.PropertyErrorf("proprietary", msg)
+		}
+		if Bool(a.commonProperties.Soc_specific) {
+			ctx.PropertyErrorf("soc_specific", msg)
+		}
+	}
+
+	if productSpecific {
+		return productSpecificModule
+	} else if deviceSpecific {
+		return deviceSpecificModule
+	} else if socSpecific {
+		return socSpecificModule
+	} else {
+		return platformModule
+	}
+}
+
 func (a *ModuleBase) androidBaseContextFactory(ctx blueprint.BaseModuleContext) androidBaseContextImpl {
 	return androidBaseContextImpl{
 		target:        a.commonProperties.CompileTarget,
 		targetPrimary: a.commonProperties.CompilePrimary,
-		vendor:        Bool(a.commonProperties.Proprietary) || Bool(a.commonProperties.Vendor),
+		kind:          determineModuleKind(a, ctx),
 		config:        ctx.Config().(Config),
 	}
 }
@@ -606,7 +687,7 @@
 	target        Target
 	targetPrimary bool
 	debug         bool
-	vendor        bool
+	kind          moduleKind
 	config        Config
 }
 
@@ -867,8 +948,20 @@
 	return DeviceConfig{a.config.deviceConfig}
 }
 
-func (a *androidBaseContextImpl) InstallOnVendorPartition() bool {
-	return a.vendor
+func (a *androidBaseContextImpl) Platform() bool {
+	return a.kind == platformModule
+}
+
+func (a *androidBaseContextImpl) DeviceSpecific() bool {
+	return a.kind == deviceSpecificModule
+}
+
+func (a *androidBaseContextImpl) SocSpecific() bool {
+	return a.kind == socSpecificModule
+}
+
+func (a *androidBaseContextImpl) ProductSpecific() bool {
+	return a.kind == productSpecificModule
 }
 
 func (a *androidModuleContext) InstallInData() bool {
diff --git a/android/paths.go b/android/paths.go
index e47b9e4..4d9c858 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -845,8 +845,12 @@
 		var partition string
 		if ctx.InstallInData() {
 			partition = "data"
-		} else if ctx.InstallOnVendorPartition() {
+		} else if ctx.SocSpecific() {
 			partition = ctx.DeviceConfig().VendorPath()
+		} else if ctx.DeviceSpecific() {
+			partition = ctx.DeviceConfig().OdmPath()
+		} else if ctx.ProductSpecific() {
+			partition = ctx.DeviceConfig().OemPath()
 		} else {
 			partition = "system"
 		}
diff --git a/android/paths_test.go b/android/paths_test.go
index 1e4ba53..110974f 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -246,12 +246,34 @@
 			ctx: &moduleInstallPathContextImpl{
 				androidBaseContextImpl: androidBaseContextImpl{
 					target: deviceTarget,
-					vendor: true,
+					kind:   socSpecificModule,
 				},
 			},
 			in:  []string{"bin", "my_test"},
 			out: "target/product/test_device/vendor/bin/my_test",
 		},
+		{
+			name: "odm binary",
+			ctx: &moduleInstallPathContextImpl{
+				androidBaseContextImpl: androidBaseContextImpl{
+					target: deviceTarget,
+					kind:   deviceSpecificModule,
+				},
+			},
+			in:  []string{"bin", "my_test"},
+			out: "target/product/test_device/odm/bin/my_test",
+		},
+		{
+			name: "oem binary",
+			ctx: &moduleInstallPathContextImpl{
+				androidBaseContextImpl: androidBaseContextImpl{
+					target: deviceTarget,
+					kind:   productSpecificModule,
+				},
+			},
+			in:  []string{"bin", "my_test"},
+			out: "target/product/test_device/oem/bin/my_test",
+		},
 
 		{
 			name: "system native test binary",
@@ -269,7 +291,31 @@
 			ctx: &moduleInstallPathContextImpl{
 				androidBaseContextImpl: androidBaseContextImpl{
 					target: deviceTarget,
-					vendor: true,
+					kind:   socSpecificModule,
+				},
+				inData: true,
+			},
+			in:  []string{"nativetest", "my_test"},
+			out: "target/product/test_device/data/nativetest/my_test",
+		},
+		{
+			name: "odm native test binary",
+			ctx: &moduleInstallPathContextImpl{
+				androidBaseContextImpl: androidBaseContextImpl{
+					target: deviceTarget,
+					kind:   deviceSpecificModule,
+				},
+				inData: true,
+			},
+			in:  []string{"nativetest", "my_test"},
+			out: "target/product/test_device/data/nativetest/my_test",
+		},
+		{
+			name: "oem native test binary",
+			ctx: &moduleInstallPathContextImpl{
+				androidBaseContextImpl: androidBaseContextImpl{
+					target: deviceTarget,
+					kind:   productSpecificModule,
 				},
 				inData: true,
 			},
@@ -293,13 +339,37 @@
 			ctx: &moduleInstallPathContextImpl{
 				androidBaseContextImpl: androidBaseContextImpl{
 					target: deviceTarget,
-					vendor: true,
+					kind:   socSpecificModule,
 				},
 				inSanitizerDir: true,
 			},
 			in:  []string{"bin", "my_test"},
 			out: "target/product/test_device/data/asan/vendor/bin/my_test",
 		},
+		{
+			name: "sanitized odm binary",
+			ctx: &moduleInstallPathContextImpl{
+				androidBaseContextImpl: androidBaseContextImpl{
+					target: deviceTarget,
+					kind:   deviceSpecificModule,
+				},
+				inSanitizerDir: true,
+			},
+			in:  []string{"bin", "my_test"},
+			out: "target/product/test_device/data/asan/odm/bin/my_test",
+		},
+		{
+			name: "sanitized oem binary",
+			ctx: &moduleInstallPathContextImpl{
+				androidBaseContextImpl: androidBaseContextImpl{
+					target: deviceTarget,
+					kind:   productSpecificModule,
+				},
+				inSanitizerDir: true,
+			},
+			in:  []string{"bin", "my_test"},
+			out: "target/product/test_device/data/asan/oem/bin/my_test",
+		},
 
 		{
 			name: "sanitized system native test binary",
@@ -318,7 +388,33 @@
 			ctx: &moduleInstallPathContextImpl{
 				androidBaseContextImpl: androidBaseContextImpl{
 					target: deviceTarget,
-					vendor: true,
+					kind:   socSpecificModule,
+				},
+				inData:         true,
+				inSanitizerDir: true,
+			},
+			in:  []string{"nativetest", "my_test"},
+			out: "target/product/test_device/data/asan/data/nativetest/my_test",
+		},
+		{
+			name: "sanitized odm native test binary",
+			ctx: &moduleInstallPathContextImpl{
+				androidBaseContextImpl: androidBaseContextImpl{
+					target: deviceTarget,
+					kind:   deviceSpecificModule,
+				},
+				inData:         true,
+				inSanitizerDir: true,
+			},
+			in:  []string{"nativetest", "my_test"},
+			out: "target/product/test_device/data/asan/data/nativetest/my_test",
+		},
+		{
+			name: "sanitized oem native test binary",
+			ctx: &moduleInstallPathContextImpl{
+				androidBaseContextImpl: androidBaseContextImpl{
+					target: deviceTarget,
+					kind:   productSpecificModule,
 				},
 				inData:         true,
 				inSanitizerDir: true,
diff --git a/android/variable.go b/android/variable.go
index 155eee5..6962b0f 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -174,6 +174,8 @@
 	CFIIncludePaths *[]string `json:",omitempty"`
 
 	VendorPath *string `json:",omitempty"`
+	OdmPath    *string `json:",omitempty"`
+	OemPath    *string `json:",omitempty"`
 
 	ClangTidy  *bool   `json:",omitempty"`
 	TidyChecks *string `json:",omitempty"`
diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go
index c6e90f0..82b5eb9 100644
--- a/androidmk/cmd/androidmk/android.go
+++ b/androidmk/cmd/androidmk/android.go
@@ -152,6 +152,8 @@
 			"LOCAL_TIDY":                     "tidy",
 			"LOCAL_PROPRIETARY_MODULE":       "proprietary",
 			"LOCAL_VENDOR_MODULE":            "vendor",
+			"LOCAL_ODM_MODULE":               "device_specific",
+			"LOCAL_OEM_MODULE":               "product_specific",
 			"LOCAL_EXPORT_PACKAGE_RESOURCES": "export_package_resources",
 			"LOCAL_PRIVILEGED_MODULE":        "privileged",
 
diff --git a/cc/cc.go b/cc/cc.go
index 04aa6a6..13d0e3b 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -425,8 +425,9 @@
 	moduleContextImpl
 }
 
-func (ctx *moduleContext) InstallOnVendorPartition() bool {
-	return ctx.ModuleContext.InstallOnVendorPartition() || (ctx.mod.useVndk() && !ctx.mod.isVndk())
+func (ctx *moduleContext) SocSpecific() bool {
+	return ctx.ModuleContext.SocSpecific() ||
+		(ctx.mod.hasVendorVariant() && ctx.mod.useVndk() && !ctx.mod.isVndk())
 }
 
 type moduleContextImpl struct {
@@ -1402,7 +1403,7 @@
 				mctx.CreateVariations(coreMode)
 			} else if Bool(props.Vendor_available) {
 				mctx.CreateVariations(coreMode, vendorMode)
-			} else if mctx.InstallOnVendorPartition() {
+			} else if mctx.SocSpecific() || mctx.DeviceSpecific() {
 				mctx.CreateVariations(vendorMode)
 			} else {
 				mctx.CreateVariations(coreMode)
@@ -1416,9 +1417,9 @@
 	}
 
 	// Sanity check
-	if m.VendorProperties.Vendor_available != nil && mctx.InstallOnVendorPartition() {
+	if m.VendorProperties.Vendor_available != nil && (mctx.SocSpecific() || mctx.DeviceSpecific()) {
 		mctx.PropertyErrorf("vendor_available",
-			"doesn't make sense at the same time as `vendor: true` or `proprietary: true`")
+			"doesn't make sense at the same time as `vendor: true`, `proprietary: true`, or `device_specific:true`")
 		return
 	}
 	if vndk := m.vndkdep; vndk != nil {
@@ -1460,8 +1461,8 @@
 		vendor := mod[1].(*Module)
 		vendor.Properties.UseVndk = true
 		squashVendorSrcs(vendor)
-	} else if mctx.InstallOnVendorPartition() && String(m.Properties.Sdk_version) == "" {
-		// This will be available in /vendor only
+	} else if (mctx.SocSpecific() || mctx.DeviceSpecific()) && String(m.Properties.Sdk_version) == "" {
+		// This will be available in /vendor (or /odm) only
 		mod := mctx.CreateVariations(vendorMode)
 		vendor := mod[0].(*Module)
 		vendor.Properties.UseVndk = true
diff --git a/cc/compiler.go b/cc/compiler.go
index c9dcf95..7e8e8b8 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -148,6 +148,9 @@
 
 	// Stores the original list of source files before being cleared by library reuse
 	OriginalSrcs []string `blueprint:"mutated"`
+
+	// Build and link with OpenMP
+	Openmp *bool `android:"arch_variant"`
 }
 
 func NewBaseCompiler() *baseCompiler {
@@ -204,6 +207,10 @@
 		deps = protoDeps(ctx, deps, &compiler.Proto, Bool(compiler.Properties.Proto.Static))
 	}
 
+	if Bool(compiler.Properties.Openmp) {
+		deps.StaticLibs = append(deps.StaticLibs, "libomp")
+	}
+
 	return deps
 }
 
@@ -494,6 +501,10 @@
 		}
 	}
 
+	if Bool(compiler.Properties.Openmp) {
+		flags.CFlags = append(flags.CFlags, "-fopenmp")
+	}
+
 	return flags
 }
 
diff --git a/cmd/merge_zips/merge_zips.go b/cmd/merge_zips/merge_zips.go
index 556dad0..df04358 100644
--- a/cmd/merge_zips/merge_zips.go
+++ b/cmd/merge_zips/merge_zips.go
@@ -19,6 +19,7 @@
 	"flag"
 	"fmt"
 	"hash/crc32"
+	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -56,11 +57,13 @@
 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)")
+	emulatePar       = flag.Bool("p", false, "merge zip entries based on par format")
 	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")
+	entrypoint       = flag.String("e", "", "par entrypoint file to insert in par")
 	ignoreDuplicates = flag.Bool("ignore-duplicates", false, "take each entry from the first zip it exists in and don't warn")
 )
 
@@ -72,7 +75,7 @@
 
 func main() {
 	flag.Usage = func() {
-		fmt.Fprintln(os.Stderr, "usage: merge_zips [-jsD] [-m manifest] output [inputs...]")
+		fmt.Fprintln(os.Stderr, "usage: merge_zips [-jpsD] [-m manifest] [-e entrypoint] output [inputs...]")
 		flag.PrintDefaults()
 	}
 
@@ -118,8 +121,13 @@
 		log.Fatal(errors.New("must specify -j when specifying a manifest via -m"))
 	}
 
+	if *entrypoint != "" && !*emulatePar {
+		log.Fatal(errors.New("must specify -p when specifying a entrypoint via -e"))
+	}
+
 	// do merge
-	err = mergeZips(readers, writer, *manifest, *sortEntries, *emulateJar, *stripDirEntries, *ignoreDuplicates)
+	err = mergeZips(readers, writer, *manifest, *entrypoint, *sortEntries, *emulateJar, *emulatePar,
+		*stripDirEntries, *ignoreDuplicates)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -210,8 +218,8 @@
 	source zipSource
 }
 
-func mergeZips(readers []namedZipReader, writer *zip.Writer, manifest string,
-	sortEntries, emulateJar, stripDirEntries, ignoreDuplicates bool) error {
+func mergeZips(readers []namedZipReader, writer *zip.Writer, manifest, entrypoint string,
+	sortEntries, emulateJar, emulatePar, stripDirEntries, ignoreDuplicates bool) error {
 
 	sourceByDest := make(map[string]zipSource, 0)
 	orderedMappings := []fileMapping{}
@@ -244,6 +252,68 @@
 		addMapping(jar.ManifestFile, fileSource)
 	}
 
+	if entrypoint != "" {
+		buf, err := ioutil.ReadFile(entrypoint)
+		if err != nil {
+			return err
+		}
+		fh := &zip.FileHeader{
+			Name:               "entry_point.txt",
+			Method:             zip.Store,
+			UncompressedSize64: uint64(len(buf)),
+		}
+		fh.SetMode(0700)
+		fh.SetModTime(jar.DefaultTime)
+		fileSource := bufferEntry{fh, buf}
+		addMapping("entry_point.txt", fileSource)
+	}
+
+	if emulatePar {
+		// the runfiles packages needs to be populated with "__init__.py".
+		newPyPkgs := []string{}
+		// the runfiles dirs have been treated as packages.
+		existingPyPkgSet := make(map[string]bool)
+		// put existing __init__.py files to a set first. This set is used for preventing
+		// generated __init__.py files from overwriting existing ones.
+		for _, namedReader := range readers {
+			for _, file := range namedReader.reader.File {
+				if filepath.Base(file.Name) != "__init__.py" {
+					continue
+				}
+				pyPkg := pathBeforeLastSlash(file.Name)
+				if _, found := existingPyPkgSet[pyPkg]; found {
+					panic(fmt.Errorf("found __init__.py path duplicates during pars merging: %q.", file.Name))
+				} else {
+					existingPyPkgSet[pyPkg] = true
+				}
+			}
+		}
+		for _, namedReader := range readers {
+			for _, file := range namedReader.reader.File {
+				var parentPath string /* the path after trimming last "/" */
+				if filepath.Base(file.Name) == "__init__.py" {
+					// for existing __init__.py files, we should trim last "/" for twice.
+					// eg. a/b/c/__init__.py ---> a/b
+					parentPath = pathBeforeLastSlash(pathBeforeLastSlash(file.Name))
+				} else {
+					parentPath = pathBeforeLastSlash(file.Name)
+				}
+				populateNewPyPkgs(parentPath, existingPyPkgSet, &newPyPkgs)
+			}
+		}
+		for _, pkg := range newPyPkgs {
+			var emptyBuf []byte
+			fh := &zip.FileHeader{
+				Name:               filepath.Join(pkg, "__init__.py"),
+				Method:             zip.Store,
+				UncompressedSize64: uint64(len(emptyBuf)),
+			}
+			fh.SetMode(0700)
+			fh.SetModTime(jar.DefaultTime)
+			fileSource := bufferEntry{fh, emptyBuf}
+			addMapping(filepath.Join(pkg, "__init__.py"), fileSource)
+		}
+	}
 	for _, namedReader := range readers {
 		_, skipStripThisZip := zipsToNotStrip[namedReader.path]
 		for _, file := range namedReader.reader.File {
@@ -305,6 +375,29 @@
 	return nil
 }
 
+// Sets the given directory and all its ancestor directories as Python packages.
+func populateNewPyPkgs(pkgPath string, existingPyPkgSet map[string]bool, newPyPkgs *[]string) {
+	for pkgPath != "" {
+		if _, found := existingPyPkgSet[pkgPath]; !found {
+			existingPyPkgSet[pkgPath] = true
+			*newPyPkgs = append(*newPyPkgs, pkgPath)
+			// Gets its ancestor directory by trimming last slash.
+			pkgPath = pathBeforeLastSlash(pkgPath)
+		} else {
+			break
+		}
+	}
+}
+
+func pathBeforeLastSlash(path string) string {
+	ret := filepath.Dir(path)
+	// filepath.Dir("abc") -> "." and filepath.Dir("/abc") -> "/".
+	if ret == "." || ret == "/" {
+		return ""
+	}
+	return ret
+}
+
 func shouldStripFile(emulateJar bool, name string) bool {
 	for _, dir := range stripDirs {
 		if strings.HasPrefix(name, dir+"/") {
diff --git a/java/jacoco.go b/java/jacoco.go
index b26b046..59f2fd3 100644
--- a/java/jacoco.go
+++ b/java/jacoco.go
@@ -17,6 +17,7 @@
 // Rules for instrumenting classes using jacoco
 
 import (
+	"fmt"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -59,41 +60,51 @@
 	})
 }
 
-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") + " "
+func (j *Module) jacocoModuleToZipCommand(ctx android.ModuleContext) string {
+	includes, err := jacocoFiltersToSpecs(j.properties.Jacoco.Include_filter)
+	if err != nil {
+		ctx.PropertyErrorf("jacoco.include_filter", "%s", err.Error())
+	}
+	excludes, err := jacocoFiltersToSpecs(j.properties.Jacoco.Exclude_filter)
+	if err != nil {
+		ctx.PropertyErrorf("jacoco.exclude_filter", "%s", err.Error())
 	}
 
+	return jacocoFiltersToZipCommand(includes, excludes)
+}
+
+func jacocoFiltersToZipCommand(includes, excludes []string) string {
+	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 {
+func jacocoFiltersToSpecs(filters []string) ([]string, error) {
 	specs := make([]string, len(filters))
+	var err error
 	for i, f := range filters {
-		specs[i] = jacocoFilterToSpec(ctx, f, property)
+		specs[i], err = jacocoFilterToSpec(f)
+		if err != nil {
+			return nil, err
+		}
 	}
-	return specs
+	return specs, nil
 }
 
-func jacocoFilterToSpec(ctx android.ModuleContext, filter string, property string) string {
+func jacocoFilterToSpec(filter string) (string, error) {
 	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")
+		return "", fmt.Errorf("'*' is only supported as the last character in a filter")
 	}
 
 	spec := strings.Replace(filter, ".", "/", -1)
@@ -102,7 +113,9 @@
 		spec += "**/*.class"
 	} else if wildcard {
 		spec += "*.class"
+	} else {
+		spec += ".class"
 	}
 
-	return spec
+	return spec, nil
 }
diff --git a/java/jacoco_test.go b/java/jacoco_test.go
new file mode 100644
index 0000000..6e8b026
--- /dev/null
+++ b/java/jacoco_test.go
@@ -0,0 +1,95 @@
+// 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
+
+import "testing"
+
+func TestJacocoFilterToSpecs(t *testing.T) {
+	testCases := []struct {
+		name, in, out string
+	}{
+		{
+			name: "class",
+			in:   "package.Class",
+			out:  "package/Class.class",
+		},
+		{
+			name: "class wildcard",
+			in:   "package.Class*",
+			out:  "package/Class*.class",
+		},
+		{
+			name: "package wildcard",
+			in:   "package.*",
+			out:  "package/**/*.class",
+		},
+		{
+			name: "all wildcard",
+			in:   "*",
+			out:  "**/*.class",
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			got, err := jacocoFilterToSpec(testCase.in)
+			if err != nil {
+				t.Error(err)
+			}
+			if got != testCase.out {
+				t.Errorf("expected %q got %q", testCase.out, got)
+			}
+		})
+	}
+}
+
+func TestJacocoFiltersToZipCommand(t *testing.T) {
+	testCases := []struct {
+		name               string
+		includes, excludes []string
+		out                string
+	}{
+		{
+			name:     "implicit wildcard",
+			includes: []string{},
+			out:      "**/*.class",
+		},
+		{
+			name:     "only include",
+			includes: []string{"package/Class.class"},
+			out:      "package/Class.class",
+		},
+		{
+			name:     "multiple includes",
+			includes: []string{"package/Class.class", "package2/Class.class"},
+			out:      "package/Class.class package2/Class.class",
+		},
+		{
+			name:     "excludes",
+			includes: []string{"package/**/*.class"},
+			excludes: []string{"package/Class.class"},
+			out:      "-x package/Class.class package/**/*.class",
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			got := jacocoFiltersToZipCommand(testCase.includes, testCase.excludes)
+			if got != testCase.out {
+				t.Errorf("expected %q got %q", testCase.out, got)
+			}
+		})
+	}
+}
diff --git a/java/java.go b/java/java.go
index 05d38f2..e6ed931 100644
--- a/java/java.go
+++ b/java/java.go
@@ -894,7 +894,7 @@
 func (j *Module) instrument(ctx android.ModuleContext, flags javaBuilderFlags,
 	classesJar android.Path, jarName string) android.Path {
 
-	specs := j.jacocoStripSpecs(ctx)
+	specs := j.jacocoModuleToZipCommand(ctx)
 
 	jacocoReportClassesFile := android.PathForModuleOut(ctx, "jacoco", "jacoco-report-classes.jar")
 	instrumentedJar := android.PathForModuleOut(ctx, "jacoco", jarName)