Break up app.go.

Test: m nothing + TreeHugger
Change-Id: I64c6d7f10530c424bc282d8111dfaf9159426f00
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 4a6aecf..4aebc99 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -258,6 +258,9 @@
 	java.RegisterJavaBuildComponents(ctx)
 	java.RegisterSystemModulesBuildComponents(ctx)
 	java.RegisterAppBuildComponents(ctx)
+	java.RegisterAppImportBuildComponents(ctx)
+	java.RegisterAppSetBuildComponents(ctx)
+	java.RegisterRuntimeResourceOverlayBuildComponents(ctx)
 	java.RegisterSdkLibraryBuildComponents(ctx)
 	ctx.RegisterSingletonType("apex_keys_text", apexKeysTextFactory)
 	ctx.RegisterModuleType("bpf", bpf.BpfFactory)
diff --git a/java/Android.bp b/java/Android.bp
index 39502b3..9c28968 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -22,6 +22,8 @@
         "androidmk.go",
         "app_builder.go",
         "app.go",
+        "app_import.go",
+        "app_set.go",
         "boot_jars.go",
         "builder.go",
         "device_host_converter.go",
@@ -46,6 +48,7 @@
         "prebuilt_apis.go",
         "proto.go",
         "robolectric.go",
+        "rro.go",
         "sdk.go",
         "sdk_library.go",
         "sdk_library_external.go",
@@ -57,6 +60,8 @@
     ],
     testSrcs: [
         "androidmk_test.go",
+        "app_import_test.go",
+        "app_set_test.go",
         "app_test.go",
         "device_host_converter_test.go",
         "dexpreopt_test.go",
@@ -66,6 +71,7 @@
         "jdeps_test.go",
         "kotlin_test.go",
         "plugin_test.go",
+        "rro_test.go",
         "sdk_test.go",
     ],
     pluginFor: ["soong_build"],
diff --git a/java/app.go b/java/app.go
index bcb610c..4369865 100755
--- a/java/app.go
+++ b/java/app.go
@@ -14,13 +14,12 @@
 
 package java
 
-// This file contains the module types for compiling Android apps.
+// This file contains the module implementations for android_app, android_test, and some more
+// related module types, including their override variants.
 
 import (
 	"path/filepath"
-	"reflect"
 	"sort"
-	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -32,12 +31,8 @@
 	"android/soong/tradefed"
 )
 
-var supportedDpis = []string{"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"}
-
 func init() {
 	RegisterAppBuildComponents(android.InitRegistrationContext)
-
-	initAndroidAppImportVariantGroupTypes()
 }
 
 func RegisterAppBuildComponents(ctx android.RegistrationContext) {
@@ -47,139 +42,6 @@
 	ctx.RegisterModuleType("android_app_certificate", AndroidAppCertificateFactory)
 	ctx.RegisterModuleType("override_android_app", OverrideAndroidAppModuleFactory)
 	ctx.RegisterModuleType("override_android_test", OverrideAndroidTestModuleFactory)
-	ctx.RegisterModuleType("override_runtime_resource_overlay", OverrideRuntimeResourceOverlayModuleFactory)
-	ctx.RegisterModuleType("android_app_import", AndroidAppImportFactory)
-	ctx.RegisterModuleType("android_test_import", AndroidTestImportFactory)
-	ctx.RegisterModuleType("runtime_resource_overlay", RuntimeResourceOverlayFactory)
-	ctx.RegisterModuleType("android_app_set", AndroidApkSetFactory)
-}
-
-type AndroidAppSetProperties struct {
-	// APK Set path
-	Set *string
-
-	// Specifies that this app should be installed to the priv-app directory,
-	// where the system will grant it additional privileges not available to
-	// normal apps.
-	Privileged *bool
-
-	// APKs in this set use prerelease SDK version
-	Prerelease *bool
-
-	// Names of modules to be overridden. Listed modules can only be other apps
-	//	(in Make or Soong).
-	Overrides []string
-}
-
-type AndroidAppSet struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-	prebuilt android.Prebuilt
-
-	properties   AndroidAppSetProperties
-	packedOutput android.WritablePath
-	installFile  string
-	apkcertsFile android.ModuleOutPath
-}
-
-func (as *AndroidAppSet) Name() string {
-	return as.prebuilt.Name(as.ModuleBase.Name())
-}
-
-func (as *AndroidAppSet) IsInstallable() bool {
-	return true
-}
-
-func (as *AndroidAppSet) Prebuilt() *android.Prebuilt {
-	return &as.prebuilt
-}
-
-func (as *AndroidAppSet) Privileged() bool {
-	return Bool(as.properties.Privileged)
-}
-
-func (as *AndroidAppSet) OutputFile() android.Path {
-	return as.packedOutput
-}
-
-func (as *AndroidAppSet) InstallFile() string {
-	return as.installFile
-}
-
-func (as *AndroidAppSet) APKCertsFile() android.Path {
-	return as.apkcertsFile
-}
-
-var TargetCpuAbi = map[string]string{
-	"arm":    "ARMEABI_V7A",
-	"arm64":  "ARM64_V8A",
-	"x86":    "X86",
-	"x86_64": "X86_64",
-}
-
-func SupportedAbis(ctx android.ModuleContext) []string {
-	abiName := func(targetIdx int, deviceArch string) string {
-		if abi, found := TargetCpuAbi[deviceArch]; found {
-			return abi
-		}
-		ctx.ModuleErrorf("Target %d has invalid Arch: %s", targetIdx, deviceArch)
-		return "BAD_ABI"
-	}
-
-	var result []string
-	for i, target := range ctx.Config().Targets[android.Android] {
-		result = append(result, abiName(i, target.Arch.ArchType.String()))
-	}
-	return result
-}
-
-func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	as.packedOutput = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
-	as.apkcertsFile = android.PathForModuleOut(ctx, "apkcerts.txt")
-	// We are assuming here that the install file in the APK
-	// set has `.apk` suffix. If it doesn't the build will fail.
-	// APK sets containing APEX files are handled elsewhere.
-	as.installFile = as.BaseModuleName() + ".apk"
-	screenDensities := "all"
-	if dpis := ctx.Config().ProductAAPTPrebuiltDPI(); len(dpis) > 0 {
-		screenDensities = strings.ToUpper(strings.Join(dpis, ","))
-	}
-	// TODO(asmundak): handle locales.
-	// TODO(asmundak): do we support device features
-	ctx.Build(pctx,
-		android.BuildParams{
-			Rule:           extractMatchingApks,
-			Description:    "Extract APKs from APK set",
-			Output:         as.packedOutput,
-			ImplicitOutput: as.apkcertsFile,
-			Inputs:         android.Paths{as.prebuilt.SingleSourcePath(ctx)},
-			Args: map[string]string{
-				"abis":              strings.Join(SupportedAbis(ctx), ","),
-				"allow-prereleased": strconv.FormatBool(proptools.Bool(as.properties.Prerelease)),
-				"screen-densities":  screenDensities,
-				"sdk-version":       ctx.Config().PlatformSdkVersion().String(),
-				"stem":              as.BaseModuleName(),
-				"apkcerts":          as.apkcertsFile.String(),
-				"partition":         as.PartitionTag(ctx.DeviceConfig()),
-			},
-		})
-}
-
-// android_app_set extracts a set of APKs based on the target device
-// configuration and installs this set as "split APKs".
-// The extracted set always contains an APK whose name is
-// _module_name_.apk and every split APK matching target device.
-// The extraction of the density-specific splits depends on
-// PRODUCT_AAPT_PREBUILT_DPI variable. If present (its value should
-// be a list density names: LDPI, MDPI, HDPI, etc.), only listed
-// splits will be extracted. Otherwise all density-specific splits
-// will be extracted.
-func AndroidApkSetFactory() android.Module {
-	module := &AndroidAppSet{}
-	module.AddProperties(&module.properties)
-	InitJavaModule(module, android.DeviceSupported)
-	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Set")
-	return module
 }
 
 // AndroidManifest.xml merging
@@ -273,15 +135,6 @@
 	Rename_resources_package *bool
 }
 
-// runtime_resource_overlay properties that can be overridden by override_runtime_resource_overlay
-type OverridableRuntimeResourceOverlayProperties struct {
-	// the package name of this app. The package name in the manifest file is used if one was not given.
-	Package_name *string
-
-	// the target package name of this overlay app. The target package name in the manifest file is used if one was not given.
-	Target_package_name *string
-}
-
 type AndroidApp struct {
 	Library
 	aapt
@@ -1298,629 +1151,6 @@
 	return m
 }
 
-type OverrideRuntimeResourceOverlay struct {
-	android.ModuleBase
-	android.OverrideModuleBase
-}
-
-func (i *OverrideRuntimeResourceOverlay) GenerateAndroidBuildActions(_ android.ModuleContext) {
-	// All the overrides happen in the base module.
-	// TODO(jungjw): Check the base module type.
-}
-
-// override_runtime_resource_overlay is used to create a module based on another
-// runtime_resource_overlay module by overriding some of its properties.
-func OverrideRuntimeResourceOverlayModuleFactory() android.Module {
-	m := &OverrideRuntimeResourceOverlay{}
-	m.AddProperties(&OverridableRuntimeResourceOverlayProperties{})
-
-	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
-	android.InitOverrideModule(m)
-	return m
-}
-
-type AndroidAppImport struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-	android.ApexModuleBase
-	prebuilt android.Prebuilt
-
-	properties   AndroidAppImportProperties
-	dpiVariants  interface{}
-	archVariants interface{}
-
-	outputFile  android.Path
-	certificate Certificate
-
-	dexpreopter
-
-	usesLibrary usesLibrary
-
-	preprocessed bool
-
-	installPath android.InstallPath
-
-	hideApexVariantFromMake bool
-}
-
-type AndroidAppImportProperties struct {
-	// A prebuilt apk to import
-	Apk *string
-
-	// The name of a certificate in the default certificate directory or an android_app_certificate
-	// module name in the form ":module". Should be empty if presigned or default_dev_cert is set.
-	Certificate *string
-
-	// Set this flag to true if the prebuilt apk is already signed. The certificate property must not
-	// be set for presigned modules.
-	Presigned *bool
-
-	// Name of the signing certificate lineage file.
-	Lineage *string
-
-	// Sign with the default system dev certificate. Must be used judiciously. Most imported apps
-	// need to either specify a specific certificate or be presigned.
-	Default_dev_cert *bool
-
-	// Specifies that this app should be installed to the priv-app directory,
-	// where the system will grant it additional privileges not available to
-	// normal apps.
-	Privileged *bool
-
-	// Names of modules to be overridden. Listed modules can only be other binaries
-	// (in Make or Soong).
-	// This does not completely prevent installation of the overridden binaries, but if both
-	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
-	// from PRODUCT_PACKAGES.
-	Overrides []string
-
-	// Optional name for the installed app. If unspecified, it is derived from the module name.
-	Filename *string
-}
-
-func (a *AndroidAppImport) IsInstallable() bool {
-	return true
-}
-
-// Updates properties with variant-specific values.
-func (a *AndroidAppImport) processVariants(ctx android.LoadHookContext) {
-	config := ctx.Config()
-
-	dpiProps := reflect.ValueOf(a.dpiVariants).Elem().FieldByName("Dpi_variants")
-	// Try DPI variant matches in the reverse-priority order so that the highest priority match
-	// overwrites everything else.
-	// TODO(jungjw): Can we optimize this by making it priority order?
-	for i := len(config.ProductAAPTPrebuiltDPI()) - 1; i >= 0; i-- {
-		MergePropertiesFromVariant(ctx, &a.properties, dpiProps, config.ProductAAPTPrebuiltDPI()[i])
-	}
-	if config.ProductAAPTPreferredConfig() != "" {
-		MergePropertiesFromVariant(ctx, &a.properties, dpiProps, config.ProductAAPTPreferredConfig())
-	}
-
-	archProps := reflect.ValueOf(a.archVariants).Elem().FieldByName("Arch")
-	archType := ctx.Config().AndroidFirstDeviceTarget.Arch.ArchType
-	MergePropertiesFromVariant(ctx, &a.properties, archProps, archType.Name)
-
-	if String(a.properties.Apk) == "" {
-		// Disable this module since the apk property is still empty after processing all matching
-		// variants. This likely means there is no matching variant, and the default variant doesn't
-		// have an apk property value either.
-		a.Disable()
-	}
-}
-
-func MergePropertiesFromVariant(ctx android.EarlyModuleContext,
-	dst interface{}, variantGroup reflect.Value, variant string) {
-	src := variantGroup.FieldByName(proptools.FieldNameForProperty(variant))
-	if !src.IsValid() {
-		return
-	}
-
-	err := proptools.ExtendMatchingProperties([]interface{}{dst}, src.Interface(), nil, proptools.OrderAppend)
-	if err != nil {
-		if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
-			ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
-		} else {
-			panic(err)
-		}
-	}
-}
-
-func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) {
-	cert := android.SrcIsModule(String(a.properties.Certificate))
-	if cert != "" {
-		ctx.AddDependency(ctx.Module(), certificateTag, cert)
-	}
-
-	a.usesLibrary.deps(ctx, true)
-}
-
-func (a *AndroidAppImport) uncompressEmbeddedJniLibs(
-	ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) {
-	// Test apps don't need their JNI libraries stored uncompressed. As a matter of fact, messing
-	// with them may invalidate pre-existing signature data.
-	if ctx.InstallInTestcases() && (Bool(a.properties.Presigned) || a.preprocessed) {
-		ctx.Build(pctx, android.BuildParams{
-			Rule:   android.Cp,
-			Output: outputPath,
-			Input:  inputPath,
-		})
-		return
-	}
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		Textf(`if (zipinfo %s 'lib/*.so' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
-		BuiltTool("zip2zip").
-		FlagWithInput("-i ", inputPath).
-		FlagWithOutput("-o ", outputPath).
-		FlagWithArg("-0 ", "'lib/**/*.so'").
-		Textf(`; else cp -f %s %s; fi`, inputPath, outputPath)
-	rule.Build("uncompress-embedded-jni-libs", "Uncompress embedded JIN libs")
-}
-
-// Returns whether this module should have the dex file stored uncompressed in the APK.
-func (a *AndroidAppImport) shouldUncompressDex(ctx android.ModuleContext) bool {
-	if ctx.Config().UnbundledBuild() || a.preprocessed {
-		return false
-	}
-
-	// Uncompress dex in APKs of privileged apps
-	if ctx.Config().UncompressPrivAppDex() && a.Privileged() {
-		return true
-	}
-
-	return shouldUncompressDex(ctx, &a.dexpreopter)
-}
-
-func (a *AndroidAppImport) uncompressDex(
-	ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) {
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		Textf(`if (zipinfo %s '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
-		BuiltTool("zip2zip").
-		FlagWithInput("-i ", inputPath).
-		FlagWithOutput("-o ", outputPath).
-		FlagWithArg("-0 ", "'classes*.dex'").
-		Textf(`; else cp -f %s %s; fi`, inputPath, outputPath)
-	rule.Build("uncompress-dex", "Uncompress dex files")
-}
-
-func (a *AndroidAppImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	a.generateAndroidBuildActions(ctx)
-}
-
-func (a *AndroidAppImport) InstallApkName() string {
-	return a.BaseModuleName()
-}
-
-func (a *AndroidAppImport) generateAndroidBuildActions(ctx android.ModuleContext) {
-	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
-	if !apexInfo.IsForPlatform() {
-		a.hideApexVariantFromMake = true
-	}
-
-	numCertPropsSet := 0
-	if String(a.properties.Certificate) != "" {
-		numCertPropsSet++
-	}
-	if Bool(a.properties.Presigned) {
-		numCertPropsSet++
-	}
-	if Bool(a.properties.Default_dev_cert) {
-		numCertPropsSet++
-	}
-	if numCertPropsSet != 1 {
-		ctx.ModuleErrorf("One and only one of certficate, presigned, and default_dev_cert properties must be set")
-	}
-
-	_, certificates := collectAppDeps(ctx, a, false, false)
-
-	// TODO: LOCAL_EXTRACT_APK/LOCAL_EXTRACT_DPI_APK
-	// TODO: LOCAL_PACKAGE_SPLITS
-
-	srcApk := a.prebuilt.SingleSourcePath(ctx)
-
-	if a.usesLibrary.enforceUsesLibraries() {
-		srcApk = a.usesLibrary.verifyUsesLibrariesAPK(ctx, srcApk)
-	}
-
-	// TODO: Install or embed JNI libraries
-
-	// Uncompress JNI libraries in the apk
-	jnisUncompressed := android.PathForModuleOut(ctx, "jnis-uncompressed", ctx.ModuleName()+".apk")
-	a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed.OutputPath)
-
-	var installDir android.InstallPath
-	if Bool(a.properties.Privileged) {
-		installDir = android.PathForModuleInstall(ctx, "priv-app", a.BaseModuleName())
-	} else if ctx.InstallInTestcases() {
-		installDir = android.PathForModuleInstall(ctx, a.BaseModuleName(), ctx.DeviceConfig().DeviceArch())
-	} else {
-		installDir = android.PathForModuleInstall(ctx, "app", a.BaseModuleName())
-	}
-
-	a.dexpreopter.installPath = installDir.Join(ctx, a.BaseModuleName()+".apk")
-	a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned)
-	a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx)
-
-	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
-	a.dexpreopter.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
-
-	a.dexpreopter.dexpreopt(ctx, jnisUncompressed)
-	if a.dexpreopter.uncompressedDex {
-		dexUncompressed := android.PathForModuleOut(ctx, "dex-uncompressed", ctx.ModuleName()+".apk")
-		a.uncompressDex(ctx, jnisUncompressed, dexUncompressed.OutputPath)
-		jnisUncompressed = dexUncompressed
-	}
-
-	apkFilename := proptools.StringDefault(a.properties.Filename, a.BaseModuleName()+".apk")
-
-	// TODO: Handle EXTERNAL
-
-	// Sign or align the package if package has not been preprocessed
-	if a.preprocessed {
-		a.outputFile = srcApk
-		a.certificate = PresignedCertificate
-	} else if !Bool(a.properties.Presigned) {
-		// If the certificate property is empty at this point, default_dev_cert must be set to true.
-		// Which makes processMainCert's behavior for the empty cert string WAI.
-		certificates = processMainCert(a.ModuleBase, String(a.properties.Certificate), certificates, ctx)
-		if len(certificates) != 1 {
-			ctx.ModuleErrorf("Unexpected number of certificates were extracted: %q", certificates)
-		}
-		a.certificate = certificates[0]
-		signed := android.PathForModuleOut(ctx, "signed", apkFilename)
-		var lineageFile android.Path
-		if lineage := String(a.properties.Lineage); lineage != "" {
-			lineageFile = android.PathForModuleSrc(ctx, lineage)
-		}
-		SignAppPackage(ctx, signed, jnisUncompressed, certificates, nil, lineageFile)
-		a.outputFile = signed
-	} else {
-		alignedApk := android.PathForModuleOut(ctx, "zip-aligned", apkFilename)
-		TransformZipAlign(ctx, alignedApk, jnisUncompressed)
-		a.outputFile = alignedApk
-		a.certificate = PresignedCertificate
-	}
-
-	// TODO: Optionally compress the output apk.
-
-	if apexInfo.IsForPlatform() {
-		a.installPath = ctx.InstallFile(installDir, apkFilename, a.outputFile)
-	}
-
-	// TODO: androidmk converter jni libs
-}
-
-func (a *AndroidAppImport) Prebuilt() *android.Prebuilt {
-	return &a.prebuilt
-}
-
-func (a *AndroidAppImport) Name() string {
-	return a.prebuilt.Name(a.ModuleBase.Name())
-}
-
-func (a *AndroidAppImport) OutputFile() android.Path {
-	return a.outputFile
-}
-
-func (a *AndroidAppImport) JacocoReportClassesFile() android.Path {
-	return nil
-}
-
-func (a *AndroidAppImport) Certificate() Certificate {
-	return a.certificate
-}
-
-var dpiVariantGroupType reflect.Type
-var archVariantGroupType reflect.Type
-
-func initAndroidAppImportVariantGroupTypes() {
-	dpiVariantGroupType = createVariantGroupType(supportedDpis, "Dpi_variants")
-
-	archNames := make([]string, len(android.ArchTypeList()))
-	for i, archType := range android.ArchTypeList() {
-		archNames[i] = archType.Name
-	}
-	archVariantGroupType = createVariantGroupType(archNames, "Arch")
-}
-
-// Populates all variant struct properties at creation time.
-func (a *AndroidAppImport) populateAllVariantStructs() {
-	a.dpiVariants = reflect.New(dpiVariantGroupType).Interface()
-	a.AddProperties(a.dpiVariants)
-
-	a.archVariants = reflect.New(archVariantGroupType).Interface()
-	a.AddProperties(a.archVariants)
-}
-
-func (a *AndroidAppImport) Privileged() bool {
-	return Bool(a.properties.Privileged)
-}
-
-func (a *AndroidAppImport) DepIsInSameApex(_ android.BaseModuleContext, _ android.Module) bool {
-	// android_app_import might have extra dependencies via uses_libs property.
-	// Don't track the dependency as we don't automatically add those libraries
-	// to the classpath. It should be explicitly added to java_libs property of APEX
-	return false
-}
-
-func (a *AndroidAppImport) sdkVersion() sdkSpec {
-	return sdkSpecFrom("")
-}
-
-func (a *AndroidAppImport) minSdkVersion() sdkSpec {
-	return sdkSpecFrom("")
-}
-
-var _ android.ApexModule = (*AndroidAppImport)(nil)
-
-// Implements android.ApexModule
-func (j *AndroidAppImport) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
-	sdkVersion android.ApiLevel) error {
-	// Do not check for prebuilts against the min_sdk_version of enclosing APEX
-	return nil
-}
-
-func createVariantGroupType(variants []string, variantGroupName string) reflect.Type {
-	props := reflect.TypeOf((*AndroidAppImportProperties)(nil))
-
-	variantFields := make([]reflect.StructField, len(variants))
-	for i, variant := range variants {
-		variantFields[i] = reflect.StructField{
-			Name: proptools.FieldNameForProperty(variant),
-			Type: props,
-		}
-	}
-
-	variantGroupStruct := reflect.StructOf(variantFields)
-	return reflect.StructOf([]reflect.StructField{
-		{
-			Name: variantGroupName,
-			Type: variantGroupStruct,
-		},
-	})
-}
-
-// android_app_import imports a prebuilt apk with additional processing specified in the module.
-// DPI-specific apk source files can be specified using dpi_variants. Example:
-//
-//     android_app_import {
-//         name: "example_import",
-//         apk: "prebuilts/example.apk",
-//         dpi_variants: {
-//             mdpi: {
-//                 apk: "prebuilts/example_mdpi.apk",
-//             },
-//             xhdpi: {
-//                 apk: "prebuilts/example_xhdpi.apk",
-//             },
-//         },
-//         certificate: "PRESIGNED",
-//     }
-func AndroidAppImportFactory() android.Module {
-	module := &AndroidAppImport{}
-	module.AddProperties(&module.properties)
-	module.AddProperties(&module.dexpreoptProperties)
-	module.AddProperties(&module.usesLibrary.usesLibraryProperties)
-	module.populateAllVariantStructs()
-	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
-		module.processVariants(ctx)
-	})
-
-	android.InitApexModule(module)
-	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
-	android.InitDefaultableModule(module)
-	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk")
-
-	return module
-}
-
-type androidTestImportProperties struct {
-	// Whether the prebuilt apk can be installed without additional processing. Default is false.
-	Preprocessed *bool
-}
-
-type AndroidTestImport struct {
-	AndroidAppImport
-
-	testProperties testProperties
-
-	testImportProperties androidTestImportProperties
-
-	data android.Paths
-}
-
-func (a *AndroidTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	a.preprocessed = Bool(a.testImportProperties.Preprocessed)
-
-	a.generateAndroidBuildActions(ctx)
-
-	a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data)
-}
-
-func (a *AndroidTestImport) InstallInTestcases() bool {
-	return true
-}
-
-// android_test_import imports a prebuilt test apk with additional processing specified in the
-// module. DPI or arch variant configurations can be made as with android_app_import.
-func AndroidTestImportFactory() android.Module {
-	module := &AndroidTestImport{}
-	module.AddProperties(&module.properties)
-	module.AddProperties(&module.dexpreoptProperties)
-	module.AddProperties(&module.testProperties)
-	module.AddProperties(&module.testImportProperties)
-	module.populateAllVariantStructs()
-	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
-		module.processVariants(ctx)
-	})
-
-	module.dexpreopter.isTest = true
-
-	android.InitApexModule(module)
-	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
-	android.InitDefaultableModule(module)
-	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk")
-
-	return module
-}
-
-type RuntimeResourceOverlay struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-	android.OverridableModuleBase
-	aapt
-
-	properties            RuntimeResourceOverlayProperties
-	overridableProperties OverridableRuntimeResourceOverlayProperties
-
-	certificate Certificate
-
-	outputFile android.Path
-	installDir android.InstallPath
-}
-
-type RuntimeResourceOverlayProperties struct {
-	// the name of a certificate in the default certificate directory or an android_app_certificate
-	// module name in the form ":module".
-	Certificate *string
-
-	// Name of the signing certificate lineage file.
-	Lineage *string
-
-	// optional theme name. If specified, the overlay package will be applied
-	// only when the ro.boot.vendor.overlay.theme system property is set to the same value.
-	Theme *string
-
-	// if not blank, set to the version of the sdk to compile against.
-	// Defaults to compiling against the current platform.
-	Sdk_version *string
-
-	// if not blank, set the minimum version of the sdk that the compiled artifacts will run against.
-	// Defaults to sdk_version if not set.
-	Min_sdk_version *string
-
-	// list of android_library modules whose resources are extracted and linked against statically
-	Static_libs []string
-
-	// list of android_app modules whose resources are extracted and linked against
-	Resource_libs []string
-
-	// Names of modules to be overridden. Listed modules can only be other overlays
-	// (in Make or Soong).
-	// This does not completely prevent installation of the overridden overlays, but if both
-	// overlays would be installed by default (in PRODUCT_PACKAGES) the other overlay will be removed
-	// from PRODUCT_PACKAGES.
-	Overrides []string
-}
-
-// RuntimeResourceOverlayModule interface is used by the apex package to gather information from
-// a RuntimeResourceOverlay module.
-type RuntimeResourceOverlayModule interface {
-	android.Module
-	OutputFile() android.Path
-	Certificate() Certificate
-	Theme() string
-}
-
-func (r *RuntimeResourceOverlay) DepsMutator(ctx android.BottomUpMutatorContext) {
-	sdkDep := decodeSdkDep(ctx, sdkContext(r))
-	if sdkDep.hasFrameworkLibs() {
-		r.aapt.deps(ctx, sdkDep)
-	}
-
-	cert := android.SrcIsModule(String(r.properties.Certificate))
-	if cert != "" {
-		ctx.AddDependency(ctx.Module(), certificateTag, cert)
-	}
-
-	ctx.AddVariationDependencies(nil, staticLibTag, r.properties.Static_libs...)
-	ctx.AddVariationDependencies(nil, libTag, r.properties.Resource_libs...)
-}
-
-func (r *RuntimeResourceOverlay) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	// Compile and link resources
-	r.aapt.hasNoCode = true
-	// Do not remove resources without default values nor dedupe resource configurations with the same value
-	aaptLinkFlags := []string{"--no-resource-deduping", "--no-resource-removal"}
-	// Allow the override of "package name" and "overlay target package name"
-	manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName())
-	if overridden || r.overridableProperties.Package_name != nil {
-		// The product override variable has a priority over the package_name property.
-		if !overridden {
-			manifestPackageName = *r.overridableProperties.Package_name
-		}
-		aaptLinkFlags = append(aaptLinkFlags, generateAaptRenamePackageFlags(manifestPackageName, false)...)
-	}
-	if r.overridableProperties.Target_package_name != nil {
-		aaptLinkFlags = append(aaptLinkFlags,
-			"--rename-overlay-target-package "+*r.overridableProperties.Target_package_name)
-	}
-	r.aapt.buildActions(ctx, r, nil, aaptLinkFlags...)
-
-	// Sign the built package
-	_, certificates := collectAppDeps(ctx, r, false, false)
-	certificates = processMainCert(r.ModuleBase, String(r.properties.Certificate), certificates, ctx)
-	signed := android.PathForModuleOut(ctx, "signed", r.Name()+".apk")
-	var lineageFile android.Path
-	if lineage := String(r.properties.Lineage); lineage != "" {
-		lineageFile = android.PathForModuleSrc(ctx, lineage)
-	}
-	SignAppPackage(ctx, signed, r.aapt.exportPackage, certificates, nil, lineageFile)
-	r.certificate = certificates[0]
-
-	r.outputFile = signed
-	r.installDir = android.PathForModuleInstall(ctx, "overlay", String(r.properties.Theme))
-	ctx.InstallFile(r.installDir, r.outputFile.Base(), r.outputFile)
-}
-
-func (r *RuntimeResourceOverlay) sdkVersion() sdkSpec {
-	return sdkSpecFrom(String(r.properties.Sdk_version))
-}
-
-func (r *RuntimeResourceOverlay) systemModules() string {
-	return ""
-}
-
-func (r *RuntimeResourceOverlay) minSdkVersion() sdkSpec {
-	if r.properties.Min_sdk_version != nil {
-		return sdkSpecFrom(*r.properties.Min_sdk_version)
-	}
-	return r.sdkVersion()
-}
-
-func (r *RuntimeResourceOverlay) targetSdkVersion() sdkSpec {
-	return r.sdkVersion()
-}
-
-func (r *RuntimeResourceOverlay) Certificate() Certificate {
-	return r.certificate
-}
-
-func (r *RuntimeResourceOverlay) OutputFile() android.Path {
-	return r.outputFile
-}
-
-func (r *RuntimeResourceOverlay) Theme() string {
-	return String(r.properties.Theme)
-}
-
-// runtime_resource_overlay generates a resource-only apk file that can overlay application and
-// system resources at run time.
-func RuntimeResourceOverlayFactory() android.Module {
-	module := &RuntimeResourceOverlay{}
-	module.AddProperties(
-		&module.properties,
-		&module.aaptProperties,
-		&module.overridableProperties)
-
-	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
-	android.InitDefaultableModule(module)
-	android.InitOverridableModule(module, &module.properties.Overrides)
-	return module
-}
-
 type UsesLibraryProperties struct {
 	// A list of shared library modules that will be listed in uses-library tags in the AndroidManifest.xml file.
 	Uses_libs []string
diff --git a/java/app_import.go b/java/app_import.go
new file mode 100644
index 0000000..2054785
--- /dev/null
+++ b/java/app_import.go
@@ -0,0 +1,484 @@
+// 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
+
+// This file contains the module implementations for android_app_import and android_test_import.
+
+import (
+	"reflect"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+func init() {
+	RegisterAppImportBuildComponents(android.InitRegistrationContext)
+
+	initAndroidAppImportVariantGroupTypes()
+}
+
+func RegisterAppImportBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("android_app_import", AndroidAppImportFactory)
+	ctx.RegisterModuleType("android_test_import", AndroidTestImportFactory)
+}
+
+type AndroidAppImport struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	android.ApexModuleBase
+	prebuilt android.Prebuilt
+
+	properties   AndroidAppImportProperties
+	dpiVariants  interface{}
+	archVariants interface{}
+
+	outputFile  android.Path
+	certificate Certificate
+
+	dexpreopter
+
+	usesLibrary usesLibrary
+
+	preprocessed bool
+
+	installPath android.InstallPath
+
+	hideApexVariantFromMake bool
+}
+
+type AndroidAppImportProperties struct {
+	// A prebuilt apk to import
+	Apk *string
+
+	// The name of a certificate in the default certificate directory or an android_app_certificate
+	// module name in the form ":module". Should be empty if presigned or default_dev_cert is set.
+	Certificate *string
+
+	// Set this flag to true if the prebuilt apk is already signed. The certificate property must not
+	// be set for presigned modules.
+	Presigned *bool
+
+	// Name of the signing certificate lineage file.
+	Lineage *string
+
+	// Sign with the default system dev certificate. Must be used judiciously. Most imported apps
+	// need to either specify a specific certificate or be presigned.
+	Default_dev_cert *bool
+
+	// Specifies that this app should be installed to the priv-app directory,
+	// where the system will grant it additional privileges not available to
+	// normal apps.
+	Privileged *bool
+
+	// Names of modules to be overridden. Listed modules can only be other binaries
+	// (in Make or Soong).
+	// This does not completely prevent installation of the overridden binaries, but if both
+	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
+	// from PRODUCT_PACKAGES.
+	Overrides []string
+
+	// Optional name for the installed app. If unspecified, it is derived from the module name.
+	Filename *string
+}
+
+func (a *AndroidAppImport) IsInstallable() bool {
+	return true
+}
+
+// Updates properties with variant-specific values.
+func (a *AndroidAppImport) processVariants(ctx android.LoadHookContext) {
+	config := ctx.Config()
+
+	dpiProps := reflect.ValueOf(a.dpiVariants).Elem().FieldByName("Dpi_variants")
+	// Try DPI variant matches in the reverse-priority order so that the highest priority match
+	// overwrites everything else.
+	// TODO(jungjw): Can we optimize this by making it priority order?
+	for i := len(config.ProductAAPTPrebuiltDPI()) - 1; i >= 0; i-- {
+		MergePropertiesFromVariant(ctx, &a.properties, dpiProps, config.ProductAAPTPrebuiltDPI()[i])
+	}
+	if config.ProductAAPTPreferredConfig() != "" {
+		MergePropertiesFromVariant(ctx, &a.properties, dpiProps, config.ProductAAPTPreferredConfig())
+	}
+
+	archProps := reflect.ValueOf(a.archVariants).Elem().FieldByName("Arch")
+	archType := ctx.Config().AndroidFirstDeviceTarget.Arch.ArchType
+	MergePropertiesFromVariant(ctx, &a.properties, archProps, archType.Name)
+
+	if String(a.properties.Apk) == "" {
+		// Disable this module since the apk property is still empty after processing all matching
+		// variants. This likely means there is no matching variant, and the default variant doesn't
+		// have an apk property value either.
+		a.Disable()
+	}
+}
+
+func MergePropertiesFromVariant(ctx android.EarlyModuleContext,
+	dst interface{}, variantGroup reflect.Value, variant string) {
+	src := variantGroup.FieldByName(proptools.FieldNameForProperty(variant))
+	if !src.IsValid() {
+		return
+	}
+
+	err := proptools.ExtendMatchingProperties([]interface{}{dst}, src.Interface(), nil, proptools.OrderAppend)
+	if err != nil {
+		if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
+			ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
+		} else {
+			panic(err)
+		}
+	}
+}
+
+func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) {
+	cert := android.SrcIsModule(String(a.properties.Certificate))
+	if cert != "" {
+		ctx.AddDependency(ctx.Module(), certificateTag, cert)
+	}
+
+	a.usesLibrary.deps(ctx, true)
+}
+
+func (a *AndroidAppImport) uncompressEmbeddedJniLibs(
+	ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) {
+	// Test apps don't need their JNI libraries stored uncompressed. As a matter of fact, messing
+	// with them may invalidate pre-existing signature data.
+	if ctx.InstallInTestcases() && (Bool(a.properties.Presigned) || a.preprocessed) {
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Output: outputPath,
+			Input:  inputPath,
+		})
+		return
+	}
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		Textf(`if (zipinfo %s 'lib/*.so' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
+		BuiltTool("zip2zip").
+		FlagWithInput("-i ", inputPath).
+		FlagWithOutput("-o ", outputPath).
+		FlagWithArg("-0 ", "'lib/**/*.so'").
+		Textf(`; else cp -f %s %s; fi`, inputPath, outputPath)
+	rule.Build("uncompress-embedded-jni-libs", "Uncompress embedded JIN libs")
+}
+
+// Returns whether this module should have the dex file stored uncompressed in the APK.
+func (a *AndroidAppImport) shouldUncompressDex(ctx android.ModuleContext) bool {
+	if ctx.Config().UnbundledBuild() || a.preprocessed {
+		return false
+	}
+
+	// Uncompress dex in APKs of privileged apps
+	if ctx.Config().UncompressPrivAppDex() && a.Privileged() {
+		return true
+	}
+
+	return shouldUncompressDex(ctx, &a.dexpreopter)
+}
+
+func (a *AndroidAppImport) uncompressDex(
+	ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) {
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		Textf(`if (zipinfo %s '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
+		BuiltTool("zip2zip").
+		FlagWithInput("-i ", inputPath).
+		FlagWithOutput("-o ", outputPath).
+		FlagWithArg("-0 ", "'classes*.dex'").
+		Textf(`; else cp -f %s %s; fi`, inputPath, outputPath)
+	rule.Build("uncompress-dex", "Uncompress dex files")
+}
+
+func (a *AndroidAppImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	a.generateAndroidBuildActions(ctx)
+}
+
+func (a *AndroidAppImport) InstallApkName() string {
+	return a.BaseModuleName()
+}
+
+func (a *AndroidAppImport) generateAndroidBuildActions(ctx android.ModuleContext) {
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	if !apexInfo.IsForPlatform() {
+		a.hideApexVariantFromMake = true
+	}
+
+	numCertPropsSet := 0
+	if String(a.properties.Certificate) != "" {
+		numCertPropsSet++
+	}
+	if Bool(a.properties.Presigned) {
+		numCertPropsSet++
+	}
+	if Bool(a.properties.Default_dev_cert) {
+		numCertPropsSet++
+	}
+	if numCertPropsSet != 1 {
+		ctx.ModuleErrorf("One and only one of certficate, presigned, and default_dev_cert properties must be set")
+	}
+
+	_, certificates := collectAppDeps(ctx, a, false, false)
+
+	// TODO: LOCAL_EXTRACT_APK/LOCAL_EXTRACT_DPI_APK
+	// TODO: LOCAL_PACKAGE_SPLITS
+
+	srcApk := a.prebuilt.SingleSourcePath(ctx)
+
+	if a.usesLibrary.enforceUsesLibraries() {
+		srcApk = a.usesLibrary.verifyUsesLibrariesAPK(ctx, srcApk)
+	}
+
+	// TODO: Install or embed JNI libraries
+
+	// Uncompress JNI libraries in the apk
+	jnisUncompressed := android.PathForModuleOut(ctx, "jnis-uncompressed", ctx.ModuleName()+".apk")
+	a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed.OutputPath)
+
+	var installDir android.InstallPath
+	if Bool(a.properties.Privileged) {
+		installDir = android.PathForModuleInstall(ctx, "priv-app", a.BaseModuleName())
+	} else if ctx.InstallInTestcases() {
+		installDir = android.PathForModuleInstall(ctx, a.BaseModuleName(), ctx.DeviceConfig().DeviceArch())
+	} else {
+		installDir = android.PathForModuleInstall(ctx, "app", a.BaseModuleName())
+	}
+
+	a.dexpreopter.installPath = installDir.Join(ctx, a.BaseModuleName()+".apk")
+	a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned)
+	a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx)
+
+	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
+	a.dexpreopter.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+
+	a.dexpreopter.dexpreopt(ctx, jnisUncompressed)
+	if a.dexpreopter.uncompressedDex {
+		dexUncompressed := android.PathForModuleOut(ctx, "dex-uncompressed", ctx.ModuleName()+".apk")
+		a.uncompressDex(ctx, jnisUncompressed, dexUncompressed.OutputPath)
+		jnisUncompressed = dexUncompressed
+	}
+
+	apkFilename := proptools.StringDefault(a.properties.Filename, a.BaseModuleName()+".apk")
+
+	// TODO: Handle EXTERNAL
+
+	// Sign or align the package if package has not been preprocessed
+	if a.preprocessed {
+		a.outputFile = srcApk
+		a.certificate = PresignedCertificate
+	} else if !Bool(a.properties.Presigned) {
+		// If the certificate property is empty at this point, default_dev_cert must be set to true.
+		// Which makes processMainCert's behavior for the empty cert string WAI.
+		certificates = processMainCert(a.ModuleBase, String(a.properties.Certificate), certificates, ctx)
+		if len(certificates) != 1 {
+			ctx.ModuleErrorf("Unexpected number of certificates were extracted: %q", certificates)
+		}
+		a.certificate = certificates[0]
+		signed := android.PathForModuleOut(ctx, "signed", apkFilename)
+		var lineageFile android.Path
+		if lineage := String(a.properties.Lineage); lineage != "" {
+			lineageFile = android.PathForModuleSrc(ctx, lineage)
+		}
+		SignAppPackage(ctx, signed, jnisUncompressed, certificates, nil, lineageFile)
+		a.outputFile = signed
+	} else {
+		alignedApk := android.PathForModuleOut(ctx, "zip-aligned", apkFilename)
+		TransformZipAlign(ctx, alignedApk, jnisUncompressed)
+		a.outputFile = alignedApk
+		a.certificate = PresignedCertificate
+	}
+
+	// TODO: Optionally compress the output apk.
+
+	if apexInfo.IsForPlatform() {
+		a.installPath = ctx.InstallFile(installDir, apkFilename, a.outputFile)
+	}
+
+	// TODO: androidmk converter jni libs
+}
+
+func (a *AndroidAppImport) Prebuilt() *android.Prebuilt {
+	return &a.prebuilt
+}
+
+func (a *AndroidAppImport) Name() string {
+	return a.prebuilt.Name(a.ModuleBase.Name())
+}
+
+func (a *AndroidAppImport) OutputFile() android.Path {
+	return a.outputFile
+}
+
+func (a *AndroidAppImport) JacocoReportClassesFile() android.Path {
+	return nil
+}
+
+func (a *AndroidAppImport) Certificate() Certificate {
+	return a.certificate
+}
+
+var dpiVariantGroupType reflect.Type
+var archVariantGroupType reflect.Type
+var supportedDpis = []string{"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"}
+
+func initAndroidAppImportVariantGroupTypes() {
+	dpiVariantGroupType = createVariantGroupType(supportedDpis, "Dpi_variants")
+
+	archNames := make([]string, len(android.ArchTypeList()))
+	for i, archType := range android.ArchTypeList() {
+		archNames[i] = archType.Name
+	}
+	archVariantGroupType = createVariantGroupType(archNames, "Arch")
+}
+
+// Populates all variant struct properties at creation time.
+func (a *AndroidAppImport) populateAllVariantStructs() {
+	a.dpiVariants = reflect.New(dpiVariantGroupType).Interface()
+	a.AddProperties(a.dpiVariants)
+
+	a.archVariants = reflect.New(archVariantGroupType).Interface()
+	a.AddProperties(a.archVariants)
+}
+
+func (a *AndroidAppImport) Privileged() bool {
+	return Bool(a.properties.Privileged)
+}
+
+func (a *AndroidAppImport) DepIsInSameApex(_ android.BaseModuleContext, _ android.Module) bool {
+	// android_app_import might have extra dependencies via uses_libs property.
+	// Don't track the dependency as we don't automatically add those libraries
+	// to the classpath. It should be explicitly added to java_libs property of APEX
+	return false
+}
+
+func (a *AndroidAppImport) sdkVersion() sdkSpec {
+	return sdkSpecFrom("")
+}
+
+func (a *AndroidAppImport) minSdkVersion() sdkSpec {
+	return sdkSpecFrom("")
+}
+
+var _ android.ApexModule = (*AndroidAppImport)(nil)
+
+// Implements android.ApexModule
+func (j *AndroidAppImport) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
+	sdkVersion android.ApiLevel) error {
+	// Do not check for prebuilts against the min_sdk_version of enclosing APEX
+	return nil
+}
+
+func createVariantGroupType(variants []string, variantGroupName string) reflect.Type {
+	props := reflect.TypeOf((*AndroidAppImportProperties)(nil))
+
+	variantFields := make([]reflect.StructField, len(variants))
+	for i, variant := range variants {
+		variantFields[i] = reflect.StructField{
+			Name: proptools.FieldNameForProperty(variant),
+			Type: props,
+		}
+	}
+
+	variantGroupStruct := reflect.StructOf(variantFields)
+	return reflect.StructOf([]reflect.StructField{
+		{
+			Name: variantGroupName,
+			Type: variantGroupStruct,
+		},
+	})
+}
+
+// android_app_import imports a prebuilt apk with additional processing specified in the module.
+// DPI-specific apk source files can be specified using dpi_variants. Example:
+//
+//     android_app_import {
+//         name: "example_import",
+//         apk: "prebuilts/example.apk",
+//         dpi_variants: {
+//             mdpi: {
+//                 apk: "prebuilts/example_mdpi.apk",
+//             },
+//             xhdpi: {
+//                 apk: "prebuilts/example_xhdpi.apk",
+//             },
+//         },
+//         certificate: "PRESIGNED",
+//     }
+func AndroidAppImportFactory() android.Module {
+	module := &AndroidAppImport{}
+	module.AddProperties(&module.properties)
+	module.AddProperties(&module.dexpreoptProperties)
+	module.AddProperties(&module.usesLibrary.usesLibraryProperties)
+	module.populateAllVariantStructs()
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		module.processVariants(ctx)
+	})
+
+	android.InitApexModule(module)
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk")
+
+	return module
+}
+
+type androidTestImportProperties struct {
+	// Whether the prebuilt apk can be installed without additional processing. Default is false.
+	Preprocessed *bool
+}
+
+type AndroidTestImport struct {
+	AndroidAppImport
+
+	testProperties testProperties
+
+	testImportProperties androidTestImportProperties
+
+	data android.Paths
+}
+
+func (a *AndroidTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	a.preprocessed = Bool(a.testImportProperties.Preprocessed)
+
+	a.generateAndroidBuildActions(ctx)
+
+	a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data)
+}
+
+func (a *AndroidTestImport) InstallInTestcases() bool {
+	return true
+}
+
+// android_test_import imports a prebuilt test apk with additional processing specified in the
+// module. DPI or arch variant configurations can be made as with android_app_import.
+func AndroidTestImportFactory() android.Module {
+	module := &AndroidTestImport{}
+	module.AddProperties(&module.properties)
+	module.AddProperties(&module.dexpreoptProperties)
+	module.AddProperties(&module.testProperties)
+	module.AddProperties(&module.testImportProperties)
+	module.populateAllVariantStructs()
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		module.processVariants(ctx)
+	})
+
+	module.dexpreopter.isTest = true
+
+	android.InitApexModule(module)
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk")
+
+	return module
+}
diff --git a/java/app_import_test.go b/java/app_import_test.go
new file mode 100644
index 0000000..3b55c81
--- /dev/null
+++ b/java/app_import_test.go
@@ -0,0 +1,495 @@
+// 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 (
+	"reflect"
+	"regexp"
+	"strings"
+	"testing"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+func TestAndroidAppImport(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "platform",
+			dex_preopt: {
+				enabled: true,
+			},
+		}
+		`)
+
+	variant := ctx.ModuleForTests("foo", "android_common")
+
+	// Check dexpreopt outputs.
+	if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil ||
+		variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil {
+		t.Errorf("can't find dexpreopt outputs")
+	}
+
+	// Check cert signing flag.
+	signedApk := variant.Output("signed/foo.apk")
+	signingFlag := signedApk.Args["certificates"]
+	expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8"
+	if expected != signingFlag {
+		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
+	}
+}
+
+func TestAndroidAppImport_NoDexPreopt(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "platform",
+			dex_preopt: {
+				enabled: false,
+			},
+		}
+		`)
+
+	variant := ctx.ModuleForTests("foo", "android_common")
+
+	// Check dexpreopt outputs. They shouldn't exist.
+	if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule != nil ||
+		variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule != nil {
+		t.Errorf("dexpreopt shouldn't have run.")
+	}
+}
+
+func TestAndroidAppImport_Presigned(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			dex_preopt: {
+				enabled: true,
+			},
+		}
+		`)
+
+	variant := ctx.ModuleForTests("foo", "android_common")
+
+	// Check dexpreopt outputs.
+	if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil ||
+		variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil {
+		t.Errorf("can't find dexpreopt outputs")
+	}
+	// Make sure signing was skipped and aligning was done.
+	if variant.MaybeOutput("signed/foo.apk").Rule != nil {
+		t.Errorf("signing rule shouldn't be included.")
+	}
+	if variant.MaybeOutput("zip-aligned/foo.apk").Rule == nil {
+		t.Errorf("can't find aligning rule")
+	}
+}
+
+func TestAndroidAppImport_SigningLineage(t *testing.T) {
+	ctx, _ := testJava(t, `
+	  android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "platform",
+			lineage: "lineage.bin",
+		}
+	`)
+
+	variant := ctx.ModuleForTests("foo", "android_common")
+
+	// Check cert signing lineage flag.
+	signedApk := variant.Output("signed/foo.apk")
+	signingFlag := signedApk.Args["flags"]
+	expected := "--lineage lineage.bin"
+	if expected != signingFlag {
+		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
+	}
+}
+
+func TestAndroidAppImport_DefaultDevCert(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			default_dev_cert: true,
+			dex_preopt: {
+				enabled: true,
+			},
+		}
+		`)
+
+	variant := ctx.ModuleForTests("foo", "android_common")
+
+	// Check dexpreopt outputs.
+	if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil ||
+		variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil {
+		t.Errorf("can't find dexpreopt outputs")
+	}
+
+	// Check cert signing flag.
+	signedApk := variant.Output("signed/foo.apk")
+	signingFlag := signedApk.Args["certificates"]
+	expected := "build/make/target/product/security/testkey.x509.pem build/make/target/product/security/testkey.pk8"
+	if expected != signingFlag {
+		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
+	}
+}
+
+func TestAndroidAppImport_DpiVariants(t *testing.T) {
+	bp := `
+		android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			dpi_variants: {
+				xhdpi: {
+					apk: "prebuilts/apk/app_xhdpi.apk",
+				},
+				xxhdpi: {
+					apk: "prebuilts/apk/app_xxhdpi.apk",
+				},
+			},
+			presigned: true,
+			dex_preopt: {
+				enabled: true,
+			},
+		}
+		`
+	testCases := []struct {
+		name                string
+		aaptPreferredConfig *string
+		aaptPrebuiltDPI     []string
+		expected            string
+	}{
+		{
+			name:                "no preferred",
+			aaptPreferredConfig: nil,
+			aaptPrebuiltDPI:     []string{},
+			expected:            "prebuilts/apk/app.apk",
+		},
+		{
+			name:                "AAPTPreferredConfig matches",
+			aaptPreferredConfig: proptools.StringPtr("xhdpi"),
+			aaptPrebuiltDPI:     []string{"xxhdpi", "ldpi"},
+			expected:            "prebuilts/apk/app_xhdpi.apk",
+		},
+		{
+			name:                "AAPTPrebuiltDPI matches",
+			aaptPreferredConfig: proptools.StringPtr("mdpi"),
+			aaptPrebuiltDPI:     []string{"xxhdpi", "xhdpi"},
+			expected:            "prebuilts/apk/app_xxhdpi.apk",
+		},
+		{
+			name:                "non-first AAPTPrebuiltDPI matches",
+			aaptPreferredConfig: proptools.StringPtr("mdpi"),
+			aaptPrebuiltDPI:     []string{"ldpi", "xhdpi"},
+			expected:            "prebuilts/apk/app_xhdpi.apk",
+		},
+		{
+			name:                "no matches",
+			aaptPreferredConfig: proptools.StringPtr("mdpi"),
+			aaptPrebuiltDPI:     []string{"ldpi", "xxxhdpi"},
+			expected:            "prebuilts/apk/app.apk",
+		},
+	}
+
+	jniRuleRe := regexp.MustCompile("^if \\(zipinfo (\\S+)")
+	for _, test := range testCases {
+		config := testAppConfig(nil, bp, nil)
+		config.TestProductVariables.AAPTPreferredConfig = test.aaptPreferredConfig
+		config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
+		ctx := testContext(config)
+
+		run(t, ctx, config)
+
+		variant := ctx.ModuleForTests("foo", "android_common")
+		jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
+		matches := jniRuleRe.FindStringSubmatch(jniRuleCommand)
+		if len(matches) != 2 {
+			t.Errorf("failed to extract the src apk path from %q", jniRuleCommand)
+		}
+		if test.expected != matches[1] {
+			t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1])
+		}
+	}
+}
+
+func TestAndroidAppImport_Filename(t *testing.T) {
+	ctx, config := testJava(t, `
+		android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+		}
+
+		android_app_import {
+			name: "bar",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			filename: "bar_sample.apk"
+		}
+		`)
+
+	testCases := []struct {
+		name     string
+		expected string
+	}{
+		{
+			name:     "foo",
+			expected: "foo.apk",
+		},
+		{
+			name:     "bar",
+			expected: "bar_sample.apk",
+		},
+	}
+
+	for _, test := range testCases {
+		variant := ctx.ModuleForTests(test.name, "android_common")
+		if variant.MaybeOutput(test.expected).Rule == nil {
+			t.Errorf("can't find output named %q - all outputs: %v", test.expected, variant.AllOutputs())
+		}
+
+		a := variant.Module().(*AndroidAppImport)
+		expectedValues := []string{test.expected}
+		actualValues := android.AndroidMkEntriesForTest(
+			t, config, "", a)[0].EntryMap["LOCAL_INSTALLED_MODULE_STEM"]
+		if !reflect.DeepEqual(actualValues, expectedValues) {
+			t.Errorf("Incorrect LOCAL_INSTALLED_MODULE_STEM value '%s', expected '%s'",
+				actualValues, expectedValues)
+		}
+	}
+}
+
+func TestAndroidAppImport_ArchVariants(t *testing.T) {
+	// The test config's target arch is ARM64.
+	testCases := []struct {
+		name     string
+		bp       string
+		expected string
+	}{
+		{
+			name: "matching arch",
+			bp: `
+				android_app_import {
+					name: "foo",
+					apk: "prebuilts/apk/app.apk",
+					arch: {
+						arm64: {
+							apk: "prebuilts/apk/app_arm64.apk",
+						},
+					},
+					presigned: true,
+					dex_preopt: {
+						enabled: true,
+					},
+				}
+			`,
+			expected: "prebuilts/apk/app_arm64.apk",
+		},
+		{
+			name: "no matching arch",
+			bp: `
+				android_app_import {
+					name: "foo",
+					apk: "prebuilts/apk/app.apk",
+					arch: {
+						arm: {
+							apk: "prebuilts/apk/app_arm.apk",
+						},
+					},
+					presigned: true,
+					dex_preopt: {
+						enabled: true,
+					},
+				}
+			`,
+			expected: "prebuilts/apk/app.apk",
+		},
+		{
+			name: "no matching arch without default",
+			bp: `
+				android_app_import {
+					name: "foo",
+					arch: {
+						arm: {
+							apk: "prebuilts/apk/app_arm.apk",
+						},
+					},
+					presigned: true,
+					dex_preopt: {
+						enabled: true,
+					},
+				}
+			`,
+			expected: "",
+		},
+	}
+
+	jniRuleRe := regexp.MustCompile("^if \\(zipinfo (\\S+)")
+	for _, test := range testCases {
+		ctx, _ := testJava(t, test.bp)
+
+		variant := ctx.ModuleForTests("foo", "android_common")
+		if test.expected == "" {
+			if variant.Module().Enabled() {
+				t.Error("module should have been disabled, but wasn't")
+			}
+			continue
+		}
+		jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
+		matches := jniRuleRe.FindStringSubmatch(jniRuleCommand)
+		if len(matches) != 2 {
+			t.Errorf("failed to extract the src apk path from %q", jniRuleCommand)
+		}
+		if test.expected != matches[1] {
+			t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1])
+		}
+	}
+}
+
+func TestAndroidAppImport_overridesDisabledAndroidApp(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			enabled: false,
+		}
+
+ 		android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "platform",
+			prefer: true,
+		}
+		`)
+
+	variant := ctx.ModuleForTests("prebuilt_foo", "android_common")
+	a := variant.Module().(*AndroidAppImport)
+	// The prebuilt module should still be enabled and active even if the source-based counterpart
+	// is disabled.
+	if !a.prebuilt.UsePrebuilt() {
+		t.Errorf("prebuilt foo module is not active")
+	}
+	if !a.Enabled() {
+		t.Errorf("prebuilt foo module is disabled")
+	}
+}
+
+func TestAndroidTestImport(t *testing.T) {
+	ctx, config := testJava(t, `
+		android_test_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			data: [
+				"testdata/data",
+			],
+		}
+		`)
+
+	test := ctx.ModuleForTests("foo", "android_common").Module().(*AndroidTestImport)
+
+	// Check android mks.
+	entries := android.AndroidMkEntriesForTest(t, config, "", test)[0]
+	expected := []string{"tests"}
+	actual := entries.EntryMap["LOCAL_MODULE_TAGS"]
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("Unexpected module tags - expected: %q, actual: %q", expected, actual)
+	}
+	expected = []string{"testdata/data:testdata/data"}
+	actual = entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"]
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("Unexpected test data - expected: %q, actual: %q", expected, actual)
+	}
+}
+
+func TestAndroidTestImport_NoJinUncompressForPresigned(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_test_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "cert/new_cert",
+			data: [
+				"testdata/data",
+			],
+		}
+
+		android_test_import {
+			name: "foo_presigned",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			data: [
+				"testdata/data",
+			],
+		}
+		`)
+
+	variant := ctx.ModuleForTests("foo", "android_common")
+	jniRule := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
+	if !strings.HasPrefix(jniRule, "if (zipinfo") {
+		t.Errorf("Unexpected JNI uncompress rule command: " + jniRule)
+	}
+
+	variant = ctx.ModuleForTests("foo_presigned", "android_common")
+	jniRule = variant.Output("jnis-uncompressed/foo_presigned.apk").BuildParams.Rule.String()
+	if jniRule != android.Cp.String() {
+		t.Errorf("Unexpected JNI uncompress rule: " + jniRule)
+	}
+	if variant.MaybeOutput("zip-aligned/foo_presigned.apk").Rule == nil {
+		t.Errorf("Presigned test apk should be aligned")
+	}
+}
+
+func TestAndroidTestImport_Preprocessed(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_test_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			preprocessed: true,
+		}
+
+		android_test_import {
+			name: "foo_cert",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "cert/new_cert",
+			preprocessed: true,
+		}
+		`)
+
+	testModules := []string{"foo", "foo_cert"}
+	for _, m := range testModules {
+		apkName := m + ".apk"
+		variant := ctx.ModuleForTests(m, "android_common")
+		jniRule := variant.Output("jnis-uncompressed/" + apkName).BuildParams.Rule.String()
+		if jniRule != android.Cp.String() {
+			t.Errorf("Unexpected JNI uncompress rule: " + jniRule)
+		}
+
+		// Make sure signing and aligning were skipped.
+		if variant.MaybeOutput("signed/"+apkName).Rule != nil {
+			t.Errorf("signing rule shouldn't be included for preprocessed.")
+		}
+		if variant.MaybeOutput("zip-aligned/"+apkName).Rule != nil {
+			t.Errorf("aligning rule shouldn't be for preprocessed")
+		}
+	}
+}
diff --git a/java/app_set.go b/java/app_set.go
new file mode 100644
index 0000000..6b25638
--- /dev/null
+++ b/java/app_set.go
@@ -0,0 +1,162 @@
+// 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
+
+// This file contains the module implementation for android_app_set.
+
+import (
+	"strconv"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+func init() {
+	RegisterAppSetBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterAppSetBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("android_app_set", AndroidAppSetFactory)
+}
+
+type AndroidAppSetProperties struct {
+	// APK Set path
+	Set *string
+
+	// Specifies that this app should be installed to the priv-app directory,
+	// where the system will grant it additional privileges not available to
+	// normal apps.
+	Privileged *bool
+
+	// APKs in this set use prerelease SDK version
+	Prerelease *bool
+
+	// Names of modules to be overridden. Listed modules can only be other apps
+	//	(in Make or Soong).
+	Overrides []string
+}
+
+type AndroidAppSet struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	prebuilt android.Prebuilt
+
+	properties   AndroidAppSetProperties
+	packedOutput android.WritablePath
+	installFile  string
+	apkcertsFile android.ModuleOutPath
+}
+
+func (as *AndroidAppSet) Name() string {
+	return as.prebuilt.Name(as.ModuleBase.Name())
+}
+
+func (as *AndroidAppSet) IsInstallable() bool {
+	return true
+}
+
+func (as *AndroidAppSet) Prebuilt() *android.Prebuilt {
+	return &as.prebuilt
+}
+
+func (as *AndroidAppSet) Privileged() bool {
+	return Bool(as.properties.Privileged)
+}
+
+func (as *AndroidAppSet) OutputFile() android.Path {
+	return as.packedOutput
+}
+
+func (as *AndroidAppSet) InstallFile() string {
+	return as.installFile
+}
+
+func (as *AndroidAppSet) APKCertsFile() android.Path {
+	return as.apkcertsFile
+}
+
+var TargetCpuAbi = map[string]string{
+	"arm":    "ARMEABI_V7A",
+	"arm64":  "ARM64_V8A",
+	"x86":    "X86",
+	"x86_64": "X86_64",
+}
+
+func SupportedAbis(ctx android.ModuleContext) []string {
+	abiName := func(targetIdx int, deviceArch string) string {
+		if abi, found := TargetCpuAbi[deviceArch]; found {
+			return abi
+		}
+		ctx.ModuleErrorf("Target %d has invalid Arch: %s", targetIdx, deviceArch)
+		return "BAD_ABI"
+	}
+
+	var result []string
+	for i, target := range ctx.Config().Targets[android.Android] {
+		result = append(result, abiName(i, target.Arch.ArchType.String()))
+	}
+	return result
+}
+
+func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	as.packedOutput = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
+	as.apkcertsFile = android.PathForModuleOut(ctx, "apkcerts.txt")
+	// We are assuming here that the install file in the APK
+	// set has `.apk` suffix. If it doesn't the build will fail.
+	// APK sets containing APEX files are handled elsewhere.
+	as.installFile = as.BaseModuleName() + ".apk"
+	screenDensities := "all"
+	if dpis := ctx.Config().ProductAAPTPrebuiltDPI(); len(dpis) > 0 {
+		screenDensities = strings.ToUpper(strings.Join(dpis, ","))
+	}
+	// TODO(asmundak): handle locales.
+	// TODO(asmundak): do we support device features
+	ctx.Build(pctx,
+		android.BuildParams{
+			Rule:           extractMatchingApks,
+			Description:    "Extract APKs from APK set",
+			Output:         as.packedOutput,
+			ImplicitOutput: as.apkcertsFile,
+			Inputs:         android.Paths{as.prebuilt.SingleSourcePath(ctx)},
+			Args: map[string]string{
+				"abis":              strings.Join(SupportedAbis(ctx), ","),
+				"allow-prereleased": strconv.FormatBool(proptools.Bool(as.properties.Prerelease)),
+				"screen-densities":  screenDensities,
+				"sdk-version":       ctx.Config().PlatformSdkVersion().String(),
+				"stem":              as.BaseModuleName(),
+				"apkcerts":          as.apkcertsFile.String(),
+				"partition":         as.PartitionTag(ctx.DeviceConfig()),
+			},
+		})
+}
+
+// android_app_set extracts a set of APKs based on the target device
+// configuration and installs this set as "split APKs".
+// The extracted set always contains an APK whose name is
+// _module_name_.apk and every split APK matching target device.
+// The extraction of the density-specific splits depends on
+// PRODUCT_AAPT_PREBUILT_DPI variable. If present (its value should
+// be a list density names: LDPI, MDPI, HDPI, etc.), only listed
+// splits will be extracted. Otherwise all density-specific splits
+// will be extracted.
+func AndroidAppSetFactory() android.Module {
+	module := &AndroidAppSet{}
+	module.AddProperties(&module.properties)
+	InitJavaModule(module, android.DeviceSupported)
+	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Set")
+	return module
+}
diff --git a/java/app_set_test.go b/java/app_set_test.go
new file mode 100644
index 0000000..d31900d
--- /dev/null
+++ b/java/app_set_test.go
@@ -0,0 +1,115 @@
+// 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 (
+	"reflect"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestAndroidAppSet(t *testing.T) {
+	ctx, config := testJava(t, `
+		android_app_set {
+			name: "foo",
+			set: "prebuilts/apks/app.apks",
+			prerelease: true,
+		}`)
+	module := ctx.ModuleForTests("foo", "android_common")
+	const packedSplitApks = "foo.zip"
+	params := module.Output(packedSplitApks)
+	if params.Rule == nil {
+		t.Errorf("expected output %s is missing", packedSplitApks)
+	}
+	if s := params.Args["allow-prereleased"]; s != "true" {
+		t.Errorf("wrong allow-prereleased value: '%s', expected 'true'", s)
+	}
+	if s := params.Args["partition"]; s != "system" {
+		t.Errorf("wrong partition value: '%s', expected 'system'", s)
+	}
+	mkEntries := android.AndroidMkEntriesForTest(t, config, "", module.Module())[0]
+	actualInstallFile := mkEntries.EntryMap["LOCAL_APK_SET_INSTALL_FILE"]
+	expectedInstallFile := []string{"foo.apk"}
+	if !reflect.DeepEqual(actualInstallFile, expectedInstallFile) {
+		t.Errorf("Unexpected LOCAL_APK_SET_INSTALL_FILE value: '%s', expected: '%s',",
+			actualInstallFile, expectedInstallFile)
+	}
+}
+
+func TestAndroidAppSet_Variants(t *testing.T) {
+	bp := `
+		android_app_set {
+			name: "foo",
+			set: "prebuilts/apks/app.apks",
+		}`
+	testCases := []struct {
+		name            string
+		targets         []android.Target
+		aaptPrebuiltDPI []string
+		sdkVersion      int
+		expected        map[string]string
+	}{
+		{
+			name: "One",
+			targets: []android.Target{
+				{Os: android.Android, Arch: android.Arch{ArchType: android.X86}},
+			},
+			aaptPrebuiltDPI: []string{"ldpi", "xxhdpi"},
+			sdkVersion:      29,
+			expected: map[string]string{
+				"abis":              "X86",
+				"allow-prereleased": "false",
+				"screen-densities":  "LDPI,XXHDPI",
+				"sdk-version":       "29",
+				"stem":              "foo",
+			},
+		},
+		{
+			name: "Two",
+			targets: []android.Target{
+				{Os: android.Android, Arch: android.Arch{ArchType: android.X86_64}},
+				{Os: android.Android, Arch: android.Arch{ArchType: android.X86}},
+			},
+			aaptPrebuiltDPI: nil,
+			sdkVersion:      30,
+			expected: map[string]string{
+				"abis":              "X86_64,X86",
+				"allow-prereleased": "false",
+				"screen-densities":  "all",
+				"sdk-version":       "30",
+				"stem":              "foo",
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		config := testAppConfig(nil, bp, nil)
+		config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
+		config.TestProductVariables.Platform_sdk_version = &test.sdkVersion
+		config.Targets[android.Android] = test.targets
+		ctx := testContext(config)
+		run(t, ctx, config)
+		module := ctx.ModuleForTests("foo", "android_common")
+		const packedSplitApks = "foo.zip"
+		params := module.Output(packedSplitApks)
+		for k, v := range test.expected {
+			if actual := params.Args[k]; actual != v {
+				t.Errorf("%s: bad build arg value for '%s': '%s', expected '%s'",
+					test.name, k, actual, v)
+			}
+		}
+	}
+}
diff --git a/java/app_test.go b/java/app_test.go
index e13c6b9..8e13154 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -18,7 +18,6 @@
 	"fmt"
 	"path/filepath"
 	"reflect"
-	"regexp"
 	"sort"
 	"strings"
 	"testing"
@@ -141,99 +140,6 @@
 	}
 }
 
-func TestAndroidAppSet(t *testing.T) {
-	ctx, config := testJava(t, `
-		android_app_set {
-			name: "foo",
-			set: "prebuilts/apks/app.apks",
-			prerelease: true,
-		}`)
-	module := ctx.ModuleForTests("foo", "android_common")
-	const packedSplitApks = "foo.zip"
-	params := module.Output(packedSplitApks)
-	if params.Rule == nil {
-		t.Errorf("expected output %s is missing", packedSplitApks)
-	}
-	if s := params.Args["allow-prereleased"]; s != "true" {
-		t.Errorf("wrong allow-prereleased value: '%s', expected 'true'", s)
-	}
-	if s := params.Args["partition"]; s != "system" {
-		t.Errorf("wrong partition value: '%s', expected 'system'", s)
-	}
-	mkEntries := android.AndroidMkEntriesForTest(t, config, "", module.Module())[0]
-	actualInstallFile := mkEntries.EntryMap["LOCAL_APK_SET_INSTALL_FILE"]
-	expectedInstallFile := []string{"foo.apk"}
-	if !reflect.DeepEqual(actualInstallFile, expectedInstallFile) {
-		t.Errorf("Unexpected LOCAL_APK_SET_INSTALL_FILE value: '%s', expected: '%s',",
-			actualInstallFile, expectedInstallFile)
-	}
-}
-
-func TestAndroidAppSet_Variants(t *testing.T) {
-	bp := `
-		android_app_set {
-			name: "foo",
-			set: "prebuilts/apks/app.apks",
-		}`
-	testCases := []struct {
-		name            string
-		targets         []android.Target
-		aaptPrebuiltDPI []string
-		sdkVersion      int
-		expected        map[string]string
-	}{
-		{
-			name: "One",
-			targets: []android.Target{
-				{Os: android.Android, Arch: android.Arch{ArchType: android.X86}},
-			},
-			aaptPrebuiltDPI: []string{"ldpi", "xxhdpi"},
-			sdkVersion:      29,
-			expected: map[string]string{
-				"abis":              "X86",
-				"allow-prereleased": "false",
-				"screen-densities":  "LDPI,XXHDPI",
-				"sdk-version":       "29",
-				"stem":              "foo",
-			},
-		},
-		{
-			name: "Two",
-			targets: []android.Target{
-				{Os: android.Android, Arch: android.Arch{ArchType: android.X86_64}},
-				{Os: android.Android, Arch: android.Arch{ArchType: android.X86}},
-			},
-			aaptPrebuiltDPI: nil,
-			sdkVersion:      30,
-			expected: map[string]string{
-				"abis":              "X86_64,X86",
-				"allow-prereleased": "false",
-				"screen-densities":  "all",
-				"sdk-version":       "30",
-				"stem":              "foo",
-			},
-		},
-	}
-
-	for _, test := range testCases {
-		config := testAppConfig(nil, bp, nil)
-		config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
-		config.TestProductVariables.Platform_sdk_version = &test.sdkVersion
-		config.Targets[android.Android] = test.targets
-		ctx := testContext(config)
-		run(t, ctx, config)
-		module := ctx.ModuleForTests("foo", "android_common")
-		const packedSplitApks = "foo.zip"
-		params := module.Output(packedSplitApks)
-		for k, v := range test.expected {
-			if actual := params.Args[k]; actual != v {
-				t.Errorf("%s: bad build arg value for '%s': '%s', expected '%s'",
-					test.name, k, actual, v)
-			}
-		}
-	}
-}
-
 func TestPlatformAPIs(t *testing.T) {
 	testJava(t, `
 		android_app {
@@ -2226,475 +2132,6 @@
 	}
 }
 
-func TestAndroidAppImport(t *testing.T) {
-	ctx, _ := testJava(t, `
-		android_app_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			certificate: "platform",
-			dex_preopt: {
-				enabled: true,
-			},
-		}
-		`)
-
-	variant := ctx.ModuleForTests("foo", "android_common")
-
-	// Check dexpreopt outputs.
-	if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil ||
-		variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil {
-		t.Errorf("can't find dexpreopt outputs")
-	}
-
-	// Check cert signing flag.
-	signedApk := variant.Output("signed/foo.apk")
-	signingFlag := signedApk.Args["certificates"]
-	expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8"
-	if expected != signingFlag {
-		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
-	}
-}
-
-func TestAndroidAppImport_NoDexPreopt(t *testing.T) {
-	ctx, _ := testJava(t, `
-		android_app_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			certificate: "platform",
-			dex_preopt: {
-				enabled: false,
-			},
-		}
-		`)
-
-	variant := ctx.ModuleForTests("foo", "android_common")
-
-	// Check dexpreopt outputs. They shouldn't exist.
-	if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule != nil ||
-		variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule != nil {
-		t.Errorf("dexpreopt shouldn't have run.")
-	}
-}
-
-func TestAndroidAppImport_Presigned(t *testing.T) {
-	ctx, _ := testJava(t, `
-		android_app_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			presigned: true,
-			dex_preopt: {
-				enabled: true,
-			},
-		}
-		`)
-
-	variant := ctx.ModuleForTests("foo", "android_common")
-
-	// Check dexpreopt outputs.
-	if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil ||
-		variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil {
-		t.Errorf("can't find dexpreopt outputs")
-	}
-	// Make sure signing was skipped and aligning was done.
-	if variant.MaybeOutput("signed/foo.apk").Rule != nil {
-		t.Errorf("signing rule shouldn't be included.")
-	}
-	if variant.MaybeOutput("zip-aligned/foo.apk").Rule == nil {
-		t.Errorf("can't find aligning rule")
-	}
-}
-
-func TestAndroidAppImport_SigningLineage(t *testing.T) {
-	ctx, _ := testJava(t, `
-	  android_app_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			certificate: "platform",
-			lineage: "lineage.bin",
-		}
-	`)
-
-	variant := ctx.ModuleForTests("foo", "android_common")
-
-	// Check cert signing lineage flag.
-	signedApk := variant.Output("signed/foo.apk")
-	signingFlag := signedApk.Args["flags"]
-	expected := "--lineage lineage.bin"
-	if expected != signingFlag {
-		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
-	}
-}
-
-func TestAndroidAppImport_DefaultDevCert(t *testing.T) {
-	ctx, _ := testJava(t, `
-		android_app_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			default_dev_cert: true,
-			dex_preopt: {
-				enabled: true,
-			},
-		}
-		`)
-
-	variant := ctx.ModuleForTests("foo", "android_common")
-
-	// Check dexpreopt outputs.
-	if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil ||
-		variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil {
-		t.Errorf("can't find dexpreopt outputs")
-	}
-
-	// Check cert signing flag.
-	signedApk := variant.Output("signed/foo.apk")
-	signingFlag := signedApk.Args["certificates"]
-	expected := "build/make/target/product/security/testkey.x509.pem build/make/target/product/security/testkey.pk8"
-	if expected != signingFlag {
-		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
-	}
-}
-
-func TestAndroidAppImport_DpiVariants(t *testing.T) {
-	bp := `
-		android_app_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			dpi_variants: {
-				xhdpi: {
-					apk: "prebuilts/apk/app_xhdpi.apk",
-				},
-				xxhdpi: {
-					apk: "prebuilts/apk/app_xxhdpi.apk",
-				},
-			},
-			presigned: true,
-			dex_preopt: {
-				enabled: true,
-			},
-		}
-		`
-	testCases := []struct {
-		name                string
-		aaptPreferredConfig *string
-		aaptPrebuiltDPI     []string
-		expected            string
-	}{
-		{
-			name:                "no preferred",
-			aaptPreferredConfig: nil,
-			aaptPrebuiltDPI:     []string{},
-			expected:            "prebuilts/apk/app.apk",
-		},
-		{
-			name:                "AAPTPreferredConfig matches",
-			aaptPreferredConfig: proptools.StringPtr("xhdpi"),
-			aaptPrebuiltDPI:     []string{"xxhdpi", "ldpi"},
-			expected:            "prebuilts/apk/app_xhdpi.apk",
-		},
-		{
-			name:                "AAPTPrebuiltDPI matches",
-			aaptPreferredConfig: proptools.StringPtr("mdpi"),
-			aaptPrebuiltDPI:     []string{"xxhdpi", "xhdpi"},
-			expected:            "prebuilts/apk/app_xxhdpi.apk",
-		},
-		{
-			name:                "non-first AAPTPrebuiltDPI matches",
-			aaptPreferredConfig: proptools.StringPtr("mdpi"),
-			aaptPrebuiltDPI:     []string{"ldpi", "xhdpi"},
-			expected:            "prebuilts/apk/app_xhdpi.apk",
-		},
-		{
-			name:                "no matches",
-			aaptPreferredConfig: proptools.StringPtr("mdpi"),
-			aaptPrebuiltDPI:     []string{"ldpi", "xxxhdpi"},
-			expected:            "prebuilts/apk/app.apk",
-		},
-	}
-
-	jniRuleRe := regexp.MustCompile("^if \\(zipinfo (\\S+)")
-	for _, test := range testCases {
-		config := testAppConfig(nil, bp, nil)
-		config.TestProductVariables.AAPTPreferredConfig = test.aaptPreferredConfig
-		config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
-		ctx := testContext(config)
-
-		run(t, ctx, config)
-
-		variant := ctx.ModuleForTests("foo", "android_common")
-		jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
-		matches := jniRuleRe.FindStringSubmatch(jniRuleCommand)
-		if len(matches) != 2 {
-			t.Errorf("failed to extract the src apk path from %q", jniRuleCommand)
-		}
-		if test.expected != matches[1] {
-			t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1])
-		}
-	}
-}
-
-func TestAndroidAppImport_Filename(t *testing.T) {
-	ctx, config := testJava(t, `
-		android_app_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			presigned: true,
-		}
-
-		android_app_import {
-			name: "bar",
-			apk: "prebuilts/apk/app.apk",
-			presigned: true,
-			filename: "bar_sample.apk"
-		}
-		`)
-
-	testCases := []struct {
-		name     string
-		expected string
-	}{
-		{
-			name:     "foo",
-			expected: "foo.apk",
-		},
-		{
-			name:     "bar",
-			expected: "bar_sample.apk",
-		},
-	}
-
-	for _, test := range testCases {
-		variant := ctx.ModuleForTests(test.name, "android_common")
-		if variant.MaybeOutput(test.expected).Rule == nil {
-			t.Errorf("can't find output named %q - all outputs: %v", test.expected, variant.AllOutputs())
-		}
-
-		a := variant.Module().(*AndroidAppImport)
-		expectedValues := []string{test.expected}
-		actualValues := android.AndroidMkEntriesForTest(
-			t, config, "", a)[0].EntryMap["LOCAL_INSTALLED_MODULE_STEM"]
-		if !reflect.DeepEqual(actualValues, expectedValues) {
-			t.Errorf("Incorrect LOCAL_INSTALLED_MODULE_STEM value '%s', expected '%s'",
-				actualValues, expectedValues)
-		}
-	}
-}
-
-func TestAndroidAppImport_ArchVariants(t *testing.T) {
-	// The test config's target arch is ARM64.
-	testCases := []struct {
-		name     string
-		bp       string
-		expected string
-	}{
-		{
-			name: "matching arch",
-			bp: `
-				android_app_import {
-					name: "foo",
-					apk: "prebuilts/apk/app.apk",
-					arch: {
-						arm64: {
-							apk: "prebuilts/apk/app_arm64.apk",
-						},
-					},
-					presigned: true,
-					dex_preopt: {
-						enabled: true,
-					},
-				}
-			`,
-			expected: "prebuilts/apk/app_arm64.apk",
-		},
-		{
-			name: "no matching arch",
-			bp: `
-				android_app_import {
-					name: "foo",
-					apk: "prebuilts/apk/app.apk",
-					arch: {
-						arm: {
-							apk: "prebuilts/apk/app_arm.apk",
-						},
-					},
-					presigned: true,
-					dex_preopt: {
-						enabled: true,
-					},
-				}
-			`,
-			expected: "prebuilts/apk/app.apk",
-		},
-		{
-			name: "no matching arch without default",
-			bp: `
-				android_app_import {
-					name: "foo",
-					arch: {
-						arm: {
-							apk: "prebuilts/apk/app_arm.apk",
-						},
-					},
-					presigned: true,
-					dex_preopt: {
-						enabled: true,
-					},
-				}
-			`,
-			expected: "",
-		},
-	}
-
-	jniRuleRe := regexp.MustCompile("^if \\(zipinfo (\\S+)")
-	for _, test := range testCases {
-		ctx, _ := testJava(t, test.bp)
-
-		variant := ctx.ModuleForTests("foo", "android_common")
-		if test.expected == "" {
-			if variant.Module().Enabled() {
-				t.Error("module should have been disabled, but wasn't")
-			}
-			continue
-		}
-		jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
-		matches := jniRuleRe.FindStringSubmatch(jniRuleCommand)
-		if len(matches) != 2 {
-			t.Errorf("failed to extract the src apk path from %q", jniRuleCommand)
-		}
-		if test.expected != matches[1] {
-			t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1])
-		}
-	}
-}
-
-func TestAndroidAppImport_overridesDisabledAndroidApp(t *testing.T) {
-	ctx, _ := testJava(t, `
-		android_app {
-			name: "foo",
-			srcs: ["a.java"],
-			enabled: false,
-		}
- 
- 		android_app_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			certificate: "platform",
-			prefer: true,
-		}
-		`)
-
-	variant := ctx.ModuleForTests("prebuilt_foo", "android_common")
-	a := variant.Module().(*AndroidAppImport)
-	// The prebuilt module should still be enabled and active even if the source-based counterpart
-	// is disabled.
-	if !a.prebuilt.UsePrebuilt() {
-		t.Errorf("prebuilt foo module is not active")
-	}
-	if !a.Enabled() {
-		t.Errorf("prebuilt foo module is disabled")
-	}
-}
-
-func TestAndroidTestImport(t *testing.T) {
-	ctx, config := testJava(t, `
-		android_test_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			presigned: true,
-			data: [
-				"testdata/data",
-			],
-		}
-		`)
-
-	test := ctx.ModuleForTests("foo", "android_common").Module().(*AndroidTestImport)
-
-	// Check android mks.
-	entries := android.AndroidMkEntriesForTest(t, config, "", test)[0]
-	expected := []string{"tests"}
-	actual := entries.EntryMap["LOCAL_MODULE_TAGS"]
-	if !reflect.DeepEqual(expected, actual) {
-		t.Errorf("Unexpected module tags - expected: %q, actual: %q", expected, actual)
-	}
-	expected = []string{"testdata/data:testdata/data"}
-	actual = entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"]
-	if !reflect.DeepEqual(expected, actual) {
-		t.Errorf("Unexpected test data - expected: %q, actual: %q", expected, actual)
-	}
-}
-
-func TestAndroidTestImport_NoJinUncompressForPresigned(t *testing.T) {
-	ctx, _ := testJava(t, `
-		android_test_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			certificate: "cert/new_cert",
-			data: [
-				"testdata/data",
-			],
-		}
-
-		android_test_import {
-			name: "foo_presigned",
-			apk: "prebuilts/apk/app.apk",
-			presigned: true,
-			data: [
-				"testdata/data",
-			],
-		}
-		`)
-
-	variant := ctx.ModuleForTests("foo", "android_common")
-	jniRule := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
-	if !strings.HasPrefix(jniRule, "if (zipinfo") {
-		t.Errorf("Unexpected JNI uncompress rule command: " + jniRule)
-	}
-
-	variant = ctx.ModuleForTests("foo_presigned", "android_common")
-	jniRule = variant.Output("jnis-uncompressed/foo_presigned.apk").BuildParams.Rule.String()
-	if jniRule != android.Cp.String() {
-		t.Errorf("Unexpected JNI uncompress rule: " + jniRule)
-	}
-	if variant.MaybeOutput("zip-aligned/foo_presigned.apk").Rule == nil {
-		t.Errorf("Presigned test apk should be aligned")
-	}
-}
-
-func TestAndroidTestImport_Preprocessed(t *testing.T) {
-	ctx, _ := testJava(t, `
-		android_test_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			presigned: true,
-			preprocessed: true,
-		}
-
-		android_test_import {
-			name: "foo_cert",
-			apk: "prebuilts/apk/app.apk",
-			certificate: "cert/new_cert",
-			preprocessed: true,
-		}
-		`)
-
-	testModules := []string{"foo", "foo_cert"}
-	for _, m := range testModules {
-		apkName := m + ".apk"
-		variant := ctx.ModuleForTests(m, "android_common")
-		jniRule := variant.Output("jnis-uncompressed/" + apkName).BuildParams.Rule.String()
-		if jniRule != android.Cp.String() {
-			t.Errorf("Unexpected JNI uncompress rule: " + jniRule)
-		}
-
-		// Make sure signing and aligning were skipped.
-		if variant.MaybeOutput("signed/"+apkName).Rule != nil {
-			t.Errorf("signing rule shouldn't be included for preprocessed.")
-		}
-		if variant.MaybeOutput("zip-aligned/"+apkName).Rule != nil {
-			t.Errorf("aligning rule shouldn't be for preprocessed")
-		}
-	}
-}
-
 func TestStl(t *testing.T) {
 	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
 		cc_library {
@@ -3237,356 +2674,6 @@
 	}
 }
 
-func TestRuntimeResourceOverlay(t *testing.T) {
-	fs := map[string][]byte{
-		"baz/res/res/values/strings.xml": nil,
-		"bar/res/res/values/strings.xml": nil,
-	}
-	bp := `
-		runtime_resource_overlay {
-			name: "foo",
-			certificate: "platform",
-			lineage: "lineage.bin",
-			product_specific: true,
-			static_libs: ["bar"],
-			resource_libs: ["baz"],
-			aaptflags: ["--keep-raw-values"],
-		}
-
-		runtime_resource_overlay {
-			name: "foo_themed",
-			certificate: "platform",
-			product_specific: true,
-			theme: "faza",
-			overrides: ["foo"],
-		}
-
-		android_library {
-			name: "bar",
-			resource_dirs: ["bar/res"],
-		}
-
-		android_app {
-			name: "baz",
-			sdk_version: "current",
-			resource_dirs: ["baz/res"],
-		}
-		`
-	config := testAppConfig(nil, bp, fs)
-	ctx := testContext(config)
-	run(t, ctx, config)
-
-	m := ctx.ModuleForTests("foo", "android_common")
-
-	// Check AAPT2 link flags.
-	aapt2Flags := m.Output("package-res.apk").Args["flags"]
-	expectedFlags := []string{"--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"}
-	absentFlags := android.RemoveListFromList(expectedFlags, strings.Split(aapt2Flags, " "))
-	if len(absentFlags) > 0 {
-		t.Errorf("expected values, %q are missing in aapt2 link flags, %q", absentFlags, aapt2Flags)
-	}
-
-	// Check overlay.list output for static_libs dependency.
-	overlayList := m.Output("aapt2/overlay.list").Inputs.Strings()
-	staticLibPackage := buildDir + "/.intermediates/bar/android_common/package-res.apk"
-	if !inList(staticLibPackage, overlayList) {
-		t.Errorf("Stactic lib res package %q missing in overlay list: %q", staticLibPackage, overlayList)
-	}
-
-	// Check AAPT2 link flags for resource_libs dependency.
-	resourceLibFlag := "-I " + buildDir + "/.intermediates/baz/android_common/package-res.apk"
-	if !strings.Contains(aapt2Flags, resourceLibFlag) {
-		t.Errorf("Resource lib flag %q missing in aapt2 link flags: %q", resourceLibFlag, aapt2Flags)
-	}
-
-	// Check cert signing flag.
-	signedApk := m.Output("signed/foo.apk")
-	lineageFlag := signedApk.Args["flags"]
-	expectedLineageFlag := "--lineage lineage.bin"
-	if expectedLineageFlag != lineageFlag {
-		t.Errorf("Incorrect signing lineage flags, expected: %q, got: %q", expectedLineageFlag, lineageFlag)
-	}
-	signingFlag := signedApk.Args["certificates"]
-	expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8"
-	if expected != signingFlag {
-		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
-	}
-	androidMkEntries := android.AndroidMkEntriesForTest(t, config, "", m.Module())[0]
-	path := androidMkEntries.EntryMap["LOCAL_CERTIFICATE"]
-	expectedPath := []string{"build/make/target/product/security/platform.x509.pem"}
-	if !reflect.DeepEqual(path, expectedPath) {
-		t.Errorf("Unexpected LOCAL_CERTIFICATE value: %v, expected: %v", path, expectedPath)
-	}
-
-	// Check device location.
-	path = androidMkEntries.EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath = []string{"/tmp/target/product/test_device/product/overlay"}
-	if !reflect.DeepEqual(path, expectedPath) {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
-	}
-
-	// A themed module has a different device location
-	m = ctx.ModuleForTests("foo_themed", "android_common")
-	androidMkEntries = android.AndroidMkEntriesForTest(t, config, "", m.Module())[0]
-	path = androidMkEntries.EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath = []string{"/tmp/target/product/test_device/product/overlay/faza"}
-	if !reflect.DeepEqual(path, expectedPath) {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
-	}
-
-	overrides := androidMkEntries.EntryMap["LOCAL_OVERRIDES_PACKAGES"]
-	expectedOverrides := []string{"foo"}
-	if !reflect.DeepEqual(overrides, expectedOverrides) {
-		t.Errorf("Unexpected LOCAL_OVERRIDES_PACKAGES value: %v, expected: %v", overrides, expectedOverrides)
-	}
-}
-
-func TestRuntimeResourceOverlay_JavaDefaults(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_defaults {
-			name: "rro_defaults",
-			theme: "default_theme",
-			product_specific: true,
-			aaptflags: ["--keep-raw-values"],
-		}
-
-		runtime_resource_overlay {
-			name: "foo_with_defaults",
-			defaults: ["rro_defaults"],
-		}
-
-		runtime_resource_overlay {
-			name: "foo_barebones",
-		}
-		`)
-
-	//
-	// RRO module with defaults
-	//
-	m := ctx.ModuleForTests("foo_with_defaults", "android_common")
-
-	// Check AAPT2 link flags.
-	aapt2Flags := strings.Split(m.Output("package-res.apk").Args["flags"], " ")
-	expectedFlags := []string{"--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"}
-	absentFlags := android.RemoveListFromList(expectedFlags, aapt2Flags)
-	if len(absentFlags) > 0 {
-		t.Errorf("expected values, %q are missing in aapt2 link flags, %q", absentFlags, aapt2Flags)
-	}
-
-	// Check device location.
-	path := android.AndroidMkEntriesForTest(t, config, "", m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath := []string{"/tmp/target/product/test_device/product/overlay/default_theme"}
-	if !reflect.DeepEqual(path, expectedPath) {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %q, expected: %q", path, expectedPath)
-	}
-
-	//
-	// RRO module without defaults
-	//
-	m = ctx.ModuleForTests("foo_barebones", "android_common")
-
-	// Check AAPT2 link flags.
-	aapt2Flags = strings.Split(m.Output("package-res.apk").Args["flags"], " ")
-	unexpectedFlags := "--keep-raw-values"
-	if inList(unexpectedFlags, aapt2Flags) {
-		t.Errorf("unexpected value, %q is present in aapt2 link flags, %q", unexpectedFlags, aapt2Flags)
-	}
-
-	// Check device location.
-	path = android.AndroidMkEntriesForTest(t, config, "", m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath = []string{"/tmp/target/product/test_device/system/overlay"}
-	if !reflect.DeepEqual(path, expectedPath) {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
-	}
-}
-
-func TestOverrideRuntimeResourceOverlay(t *testing.T) {
-	ctx, _ := testJava(t, `
-		runtime_resource_overlay {
-			name: "foo_overlay",
-			certificate: "platform",
-			product_specific: true,
-			sdk_version: "current",
-		}
-
-		override_runtime_resource_overlay {
-			name: "bar_overlay",
-			base: "foo_overlay",
-			package_name: "com.android.bar.overlay",
-			target_package_name: "com.android.bar",
-		}
-		`)
-
-	expectedVariants := []struct {
-		moduleName        string
-		variantName       string
-		apkPath           string
-		overrides         []string
-		targetVariant     string
-		packageFlag       string
-		targetPackageFlag string
-	}{
-		{
-			variantName:       "android_common",
-			apkPath:           "/target/product/test_device/product/overlay/foo_overlay.apk",
-			overrides:         nil,
-			targetVariant:     "android_common",
-			packageFlag:       "",
-			targetPackageFlag: "",
-		},
-		{
-			variantName:       "android_common_bar_overlay",
-			apkPath:           "/target/product/test_device/product/overlay/bar_overlay.apk",
-			overrides:         []string{"foo_overlay"},
-			targetVariant:     "android_common_bar",
-			packageFlag:       "com.android.bar.overlay",
-			targetPackageFlag: "com.android.bar",
-		},
-	}
-	for _, expected := range expectedVariants {
-		variant := ctx.ModuleForTests("foo_overlay", expected.variantName)
-
-		// Check the final apk name
-		outputs := variant.AllOutputs()
-		expectedApkPath := buildDir + expected.apkPath
-		found := false
-		for _, o := range outputs {
-			if o == expectedApkPath {
-				found = true
-				break
-			}
-		}
-		if !found {
-			t.Errorf("Can't find %q in output files.\nAll outputs:%v", expectedApkPath, outputs)
-		}
-
-		// Check if the overrides field values are correctly aggregated.
-		mod := variant.Module().(*RuntimeResourceOverlay)
-		if !reflect.DeepEqual(expected.overrides, mod.properties.Overrides) {
-			t.Errorf("Incorrect overrides property value, expected: %q, got: %q",
-				expected.overrides, mod.properties.Overrides)
-		}
-
-		// Check aapt2 flags.
-		res := variant.Output("package-res.apk")
-		aapt2Flags := res.Args["flags"]
-		checkAapt2LinkFlag(t, aapt2Flags, "rename-manifest-package", expected.packageFlag)
-		checkAapt2LinkFlag(t, aapt2Flags, "rename-resources-package", "")
-		checkAapt2LinkFlag(t, aapt2Flags, "rename-overlay-target-package", expected.targetPackageFlag)
-	}
-}
-
-func TestEnforceRRO_propagatesToDependencies(t *testing.T) {
-	testCases := []struct {
-		name                    string
-		enforceRROTargets       []string
-		enforceRROExemptTargets []string
-		rroDirs                 map[string][]string
-	}{
-		{
-			name:                    "no RRO",
-			enforceRROTargets:       nil,
-			enforceRROExemptTargets: nil,
-			rroDirs: map[string][]string{
-				"foo": nil,
-				"bar": nil,
-			},
-		},
-		{
-			name:                    "enforce RRO on all",
-			enforceRROTargets:       []string{"*"},
-			enforceRROExemptTargets: nil,
-			rroDirs: map[string][]string{
-				"foo": {"product/vendor/blah/overlay/lib2/res"},
-				"bar": {"product/vendor/blah/overlay/lib2/res"},
-			},
-		},
-		{
-			name:                    "enforce RRO on foo",
-			enforceRROTargets:       []string{"foo"},
-			enforceRROExemptTargets: nil,
-			rroDirs: map[string][]string{
-				"foo": {"product/vendor/blah/overlay/lib2/res"},
-				"bar": {"product/vendor/blah/overlay/lib2/res"},
-			},
-		},
-		{
-			name:                    "enforce RRO on foo, bar exempted",
-			enforceRROTargets:       []string{"foo"},
-			enforceRROExemptTargets: []string{"bar"},
-			rroDirs: map[string][]string{
-				"foo": {"product/vendor/blah/overlay/lib2/res"},
-				"bar": nil,
-			},
-		},
-	}
-
-	productResourceOverlays := []string{
-		"product/vendor/blah/overlay",
-	}
-
-	fs := map[string][]byte{
-		"lib2/res/values/strings.xml":                             nil,
-		"product/vendor/blah/overlay/lib2/res/values/strings.xml": nil,
-	}
-
-	bp := `
-			android_app {
-				name: "foo",
-				sdk_version: "current",
-				resource_dirs: [],
-				static_libs: ["lib"],
-			}
-
-			android_app {
-				name: "bar",
-				sdk_version: "current",
-				resource_dirs: [],
-				static_libs: ["lib"],
-			}
-
-			android_library {
-				name: "lib",
-				sdk_version: "current",
-				resource_dirs: [],
-				static_libs: ["lib2"],
-			}
-
-			android_library {
-				name: "lib2",
-				sdk_version: "current",
-				resource_dirs: ["lib2/res"],
-			}
-		`
-
-	for _, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			config := testAppConfig(nil, bp, fs)
-			config.TestProductVariables.ProductResourceOverlays = productResourceOverlays
-			if testCase.enforceRROTargets != nil {
-				config.TestProductVariables.EnforceRROTargets = testCase.enforceRROTargets
-			}
-			if testCase.enforceRROExemptTargets != nil {
-				config.TestProductVariables.EnforceRROExemptedTargets = testCase.enforceRROExemptTargets
-			}
-
-			ctx := testContext(config)
-			run(t, ctx, config)
-
-			modules := []string{"foo", "bar"}
-			for _, moduleName := range modules {
-				module := ctx.ModuleForTests(moduleName, "android_common")
-				mkEntries := android.AndroidMkEntriesForTest(t, config, "", module.Module())[0]
-				actualRRODirs := mkEntries.EntryMap["LOCAL_SOONG_PRODUCT_RRO_DIRS"]
-				if !reflect.DeepEqual(actualRRODirs, testCase.rroDirs[moduleName]) {
-					t.Errorf("exected %s LOCAL_SOONG_PRODUCT_RRO_DIRS entry: %v\ngot:%q",
-						moduleName, testCase.rroDirs[moduleName], actualRRODirs)
-				}
-			}
-		})
-	}
-}
-
 func TestExportedProguardFlagFiles(t *testing.T) {
 	ctx, _ := testJava(t, `
 		android_app {
diff --git a/java/java_test.go b/java/java_test.go
index f7cf03f..de5c55c 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -75,8 +75,11 @@
 	ctx := android.NewTestArchContext(config)
 	RegisterJavaBuildComponents(ctx)
 	RegisterAppBuildComponents(ctx)
+	RegisterAppImportBuildComponents(ctx)
+	RegisterAppSetBuildComponents(ctx)
 	RegisterAARBuildComponents(ctx)
 	RegisterGenRuleBuildComponents(ctx)
+	RegisterRuntimeResourceOverlayBuildComponents(ctx)
 	RegisterSystemModulesBuildComponents(ctx)
 	ctx.RegisterModuleType("java_plugin", PluginFactory)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
diff --git a/java/rro.go b/java/rro.go
new file mode 100644
index 0000000..98cd379
--- /dev/null
+++ b/java/rro.go
@@ -0,0 +1,214 @@
+// 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
+
+// This file contains the module implementations for runtime_resource_overlay and
+// override_runtime_resource_overlay.
+
+import "android/soong/android"
+
+func init() {
+	RegisterRuntimeResourceOverlayBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterRuntimeResourceOverlayBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("runtime_resource_overlay", RuntimeResourceOverlayFactory)
+	ctx.RegisterModuleType("override_runtime_resource_overlay", OverrideRuntimeResourceOverlayModuleFactory)
+}
+
+type RuntimeResourceOverlay struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	android.OverridableModuleBase
+	aapt
+
+	properties            RuntimeResourceOverlayProperties
+	overridableProperties OverridableRuntimeResourceOverlayProperties
+
+	certificate Certificate
+
+	outputFile android.Path
+	installDir android.InstallPath
+}
+
+type RuntimeResourceOverlayProperties struct {
+	// the name of a certificate in the default certificate directory or an android_app_certificate
+	// module name in the form ":module".
+	Certificate *string
+
+	// Name of the signing certificate lineage file.
+	Lineage *string
+
+	// optional theme name. If specified, the overlay package will be applied
+	// only when the ro.boot.vendor.overlay.theme system property is set to the same value.
+	Theme *string
+
+	// if not blank, set to the version of the sdk to compile against.
+	// Defaults to compiling against the current platform.
+	Sdk_version *string
+
+	// if not blank, set the minimum version of the sdk that the compiled artifacts will run against.
+	// Defaults to sdk_version if not set.
+	Min_sdk_version *string
+
+	// list of android_library modules whose resources are extracted and linked against statically
+	Static_libs []string
+
+	// list of android_app modules whose resources are extracted and linked against
+	Resource_libs []string
+
+	// Names of modules to be overridden. Listed modules can only be other overlays
+	// (in Make or Soong).
+	// This does not completely prevent installation of the overridden overlays, but if both
+	// overlays would be installed by default (in PRODUCT_PACKAGES) the other overlay will be removed
+	// from PRODUCT_PACKAGES.
+	Overrides []string
+}
+
+// RuntimeResourceOverlayModule interface is used by the apex package to gather information from
+// a RuntimeResourceOverlay module.
+type RuntimeResourceOverlayModule interface {
+	android.Module
+	OutputFile() android.Path
+	Certificate() Certificate
+	Theme() string
+}
+
+func (r *RuntimeResourceOverlay) DepsMutator(ctx android.BottomUpMutatorContext) {
+	sdkDep := decodeSdkDep(ctx, sdkContext(r))
+	if sdkDep.hasFrameworkLibs() {
+		r.aapt.deps(ctx, sdkDep)
+	}
+
+	cert := android.SrcIsModule(String(r.properties.Certificate))
+	if cert != "" {
+		ctx.AddDependency(ctx.Module(), certificateTag, cert)
+	}
+
+	ctx.AddVariationDependencies(nil, staticLibTag, r.properties.Static_libs...)
+	ctx.AddVariationDependencies(nil, libTag, r.properties.Resource_libs...)
+}
+
+func (r *RuntimeResourceOverlay) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Compile and link resources
+	r.aapt.hasNoCode = true
+	// Do not remove resources without default values nor dedupe resource configurations with the same value
+	aaptLinkFlags := []string{"--no-resource-deduping", "--no-resource-removal"}
+	// Allow the override of "package name" and "overlay target package name"
+	manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName())
+	if overridden || r.overridableProperties.Package_name != nil {
+		// The product override variable has a priority over the package_name property.
+		if !overridden {
+			manifestPackageName = *r.overridableProperties.Package_name
+		}
+		aaptLinkFlags = append(aaptLinkFlags, generateAaptRenamePackageFlags(manifestPackageName, false)...)
+	}
+	if r.overridableProperties.Target_package_name != nil {
+		aaptLinkFlags = append(aaptLinkFlags,
+			"--rename-overlay-target-package "+*r.overridableProperties.Target_package_name)
+	}
+	r.aapt.buildActions(ctx, r, nil, aaptLinkFlags...)
+
+	// Sign the built package
+	_, certificates := collectAppDeps(ctx, r, false, false)
+	certificates = processMainCert(r.ModuleBase, String(r.properties.Certificate), certificates, ctx)
+	signed := android.PathForModuleOut(ctx, "signed", r.Name()+".apk")
+	var lineageFile android.Path
+	if lineage := String(r.properties.Lineage); lineage != "" {
+		lineageFile = android.PathForModuleSrc(ctx, lineage)
+	}
+	SignAppPackage(ctx, signed, r.aapt.exportPackage, certificates, nil, lineageFile)
+	r.certificate = certificates[0]
+
+	r.outputFile = signed
+	r.installDir = android.PathForModuleInstall(ctx, "overlay", String(r.properties.Theme))
+	ctx.InstallFile(r.installDir, r.outputFile.Base(), r.outputFile)
+}
+
+func (r *RuntimeResourceOverlay) sdkVersion() sdkSpec {
+	return sdkSpecFrom(String(r.properties.Sdk_version))
+}
+
+func (r *RuntimeResourceOverlay) systemModules() string {
+	return ""
+}
+
+func (r *RuntimeResourceOverlay) minSdkVersion() sdkSpec {
+	if r.properties.Min_sdk_version != nil {
+		return sdkSpecFrom(*r.properties.Min_sdk_version)
+	}
+	return r.sdkVersion()
+}
+
+func (r *RuntimeResourceOverlay) targetSdkVersion() sdkSpec {
+	return r.sdkVersion()
+}
+
+func (r *RuntimeResourceOverlay) Certificate() Certificate {
+	return r.certificate
+}
+
+func (r *RuntimeResourceOverlay) OutputFile() android.Path {
+	return r.outputFile
+}
+
+func (r *RuntimeResourceOverlay) Theme() string {
+	return String(r.properties.Theme)
+}
+
+// runtime_resource_overlay generates a resource-only apk file that can overlay application and
+// system resources at run time.
+func RuntimeResourceOverlayFactory() android.Module {
+	module := &RuntimeResourceOverlay{}
+	module.AddProperties(
+		&module.properties,
+		&module.aaptProperties,
+		&module.overridableProperties)
+
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	android.InitOverridableModule(module, &module.properties.Overrides)
+	return module
+}
+
+// runtime_resource_overlay properties that can be overridden by override_runtime_resource_overlay
+type OverridableRuntimeResourceOverlayProperties struct {
+	// the package name of this app. The package name in the manifest file is used if one was not given.
+	Package_name *string
+
+	// the target package name of this overlay app. The target package name in the manifest file is used if one was not given.
+	Target_package_name *string
+}
+
+type OverrideRuntimeResourceOverlay struct {
+	android.ModuleBase
+	android.OverrideModuleBase
+}
+
+func (i *OverrideRuntimeResourceOverlay) GenerateAndroidBuildActions(_ android.ModuleContext) {
+	// All the overrides happen in the base module.
+	// TODO(jungjw): Check the base module type.
+}
+
+// override_runtime_resource_overlay is used to create a module based on another
+// runtime_resource_overlay module by overriding some of its properties.
+func OverrideRuntimeResourceOverlayModuleFactory() android.Module {
+	m := &OverrideRuntimeResourceOverlay{}
+	m.AddProperties(&OverridableRuntimeResourceOverlayProperties{})
+
+	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	android.InitOverrideModule(m)
+	return m
+}
diff --git a/java/rro_test.go b/java/rro_test.go
new file mode 100644
index 0000000..345f2ee
--- /dev/null
+++ b/java/rro_test.go
@@ -0,0 +1,373 @@
+// 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 (
+	"reflect"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestRuntimeResourceOverlay(t *testing.T) {
+	fs := map[string][]byte{
+		"baz/res/res/values/strings.xml": nil,
+		"bar/res/res/values/strings.xml": nil,
+	}
+	bp := `
+		runtime_resource_overlay {
+			name: "foo",
+			certificate: "platform",
+			lineage: "lineage.bin",
+			product_specific: true,
+			static_libs: ["bar"],
+			resource_libs: ["baz"],
+			aaptflags: ["--keep-raw-values"],
+		}
+
+		runtime_resource_overlay {
+			name: "foo_themed",
+			certificate: "platform",
+			product_specific: true,
+			theme: "faza",
+			overrides: ["foo"],
+		}
+
+		android_library {
+			name: "bar",
+			resource_dirs: ["bar/res"],
+		}
+
+		android_app {
+			name: "baz",
+			sdk_version: "current",
+			resource_dirs: ["baz/res"],
+		}
+		`
+	config := testAppConfig(nil, bp, fs)
+	ctx := testContext(config)
+	run(t, ctx, config)
+
+	m := ctx.ModuleForTests("foo", "android_common")
+
+	// Check AAPT2 link flags.
+	aapt2Flags := m.Output("package-res.apk").Args["flags"]
+	expectedFlags := []string{"--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"}
+	absentFlags := android.RemoveListFromList(expectedFlags, strings.Split(aapt2Flags, " "))
+	if len(absentFlags) > 0 {
+		t.Errorf("expected values, %q are missing in aapt2 link flags, %q", absentFlags, aapt2Flags)
+	}
+
+	// Check overlay.list output for static_libs dependency.
+	overlayList := m.Output("aapt2/overlay.list").Inputs.Strings()
+	staticLibPackage := buildDir + "/.intermediates/bar/android_common/package-res.apk"
+	if !inList(staticLibPackage, overlayList) {
+		t.Errorf("Stactic lib res package %q missing in overlay list: %q", staticLibPackage, overlayList)
+	}
+
+	// Check AAPT2 link flags for resource_libs dependency.
+	resourceLibFlag := "-I " + buildDir + "/.intermediates/baz/android_common/package-res.apk"
+	if !strings.Contains(aapt2Flags, resourceLibFlag) {
+		t.Errorf("Resource lib flag %q missing in aapt2 link flags: %q", resourceLibFlag, aapt2Flags)
+	}
+
+	// Check cert signing flag.
+	signedApk := m.Output("signed/foo.apk")
+	lineageFlag := signedApk.Args["flags"]
+	expectedLineageFlag := "--lineage lineage.bin"
+	if expectedLineageFlag != lineageFlag {
+		t.Errorf("Incorrect signing lineage flags, expected: %q, got: %q", expectedLineageFlag, lineageFlag)
+	}
+	signingFlag := signedApk.Args["certificates"]
+	expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8"
+	if expected != signingFlag {
+		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
+	}
+	androidMkEntries := android.AndroidMkEntriesForTest(t, config, "", m.Module())[0]
+	path := androidMkEntries.EntryMap["LOCAL_CERTIFICATE"]
+	expectedPath := []string{"build/make/target/product/security/platform.x509.pem"}
+	if !reflect.DeepEqual(path, expectedPath) {
+		t.Errorf("Unexpected LOCAL_CERTIFICATE value: %v, expected: %v", path, expectedPath)
+	}
+
+	// Check device location.
+	path = androidMkEntries.EntryMap["LOCAL_MODULE_PATH"]
+	expectedPath = []string{"/tmp/target/product/test_device/product/overlay"}
+	if !reflect.DeepEqual(path, expectedPath) {
+		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
+	}
+
+	// A themed module has a different device location
+	m = ctx.ModuleForTests("foo_themed", "android_common")
+	androidMkEntries = android.AndroidMkEntriesForTest(t, config, "", m.Module())[0]
+	path = androidMkEntries.EntryMap["LOCAL_MODULE_PATH"]
+	expectedPath = []string{"/tmp/target/product/test_device/product/overlay/faza"}
+	if !reflect.DeepEqual(path, expectedPath) {
+		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
+	}
+
+	overrides := androidMkEntries.EntryMap["LOCAL_OVERRIDES_PACKAGES"]
+	expectedOverrides := []string{"foo"}
+	if !reflect.DeepEqual(overrides, expectedOverrides) {
+		t.Errorf("Unexpected LOCAL_OVERRIDES_PACKAGES value: %v, expected: %v", overrides, expectedOverrides)
+	}
+}
+
+func TestRuntimeResourceOverlay_JavaDefaults(t *testing.T) {
+	ctx, config := testJava(t, `
+		java_defaults {
+			name: "rro_defaults",
+			theme: "default_theme",
+			product_specific: true,
+			aaptflags: ["--keep-raw-values"],
+		}
+
+		runtime_resource_overlay {
+			name: "foo_with_defaults",
+			defaults: ["rro_defaults"],
+		}
+
+		runtime_resource_overlay {
+			name: "foo_barebones",
+		}
+		`)
+
+	//
+	// RRO module with defaults
+	//
+	m := ctx.ModuleForTests("foo_with_defaults", "android_common")
+
+	// Check AAPT2 link flags.
+	aapt2Flags := strings.Split(m.Output("package-res.apk").Args["flags"], " ")
+	expectedFlags := []string{"--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"}
+	absentFlags := android.RemoveListFromList(expectedFlags, aapt2Flags)
+	if len(absentFlags) > 0 {
+		t.Errorf("expected values, %q are missing in aapt2 link flags, %q", absentFlags, aapt2Flags)
+	}
+
+	// Check device location.
+	path := android.AndroidMkEntriesForTest(t, config, "", m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	expectedPath := []string{"/tmp/target/product/test_device/product/overlay/default_theme"}
+	if !reflect.DeepEqual(path, expectedPath) {
+		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %q, expected: %q", path, expectedPath)
+	}
+
+	//
+	// RRO module without defaults
+	//
+	m = ctx.ModuleForTests("foo_barebones", "android_common")
+
+	// Check AAPT2 link flags.
+	aapt2Flags = strings.Split(m.Output("package-res.apk").Args["flags"], " ")
+	unexpectedFlags := "--keep-raw-values"
+	if inList(unexpectedFlags, aapt2Flags) {
+		t.Errorf("unexpected value, %q is present in aapt2 link flags, %q", unexpectedFlags, aapt2Flags)
+	}
+
+	// Check device location.
+	path = android.AndroidMkEntriesForTest(t, config, "", m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	expectedPath = []string{"/tmp/target/product/test_device/system/overlay"}
+	if !reflect.DeepEqual(path, expectedPath) {
+		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
+	}
+}
+
+func TestOverrideRuntimeResourceOverlay(t *testing.T) {
+	ctx, _ := testJava(t, `
+		runtime_resource_overlay {
+			name: "foo_overlay",
+			certificate: "platform",
+			product_specific: true,
+			sdk_version: "current",
+		}
+
+		override_runtime_resource_overlay {
+			name: "bar_overlay",
+			base: "foo_overlay",
+			package_name: "com.android.bar.overlay",
+			target_package_name: "com.android.bar",
+		}
+		`)
+
+	expectedVariants := []struct {
+		moduleName        string
+		variantName       string
+		apkPath           string
+		overrides         []string
+		targetVariant     string
+		packageFlag       string
+		targetPackageFlag string
+	}{
+		{
+			variantName:       "android_common",
+			apkPath:           "/target/product/test_device/product/overlay/foo_overlay.apk",
+			overrides:         nil,
+			targetVariant:     "android_common",
+			packageFlag:       "",
+			targetPackageFlag: "",
+		},
+		{
+			variantName:       "android_common_bar_overlay",
+			apkPath:           "/target/product/test_device/product/overlay/bar_overlay.apk",
+			overrides:         []string{"foo_overlay"},
+			targetVariant:     "android_common_bar",
+			packageFlag:       "com.android.bar.overlay",
+			targetPackageFlag: "com.android.bar",
+		},
+	}
+	for _, expected := range expectedVariants {
+		variant := ctx.ModuleForTests("foo_overlay", expected.variantName)
+
+		// Check the final apk name
+		outputs := variant.AllOutputs()
+		expectedApkPath := buildDir + expected.apkPath
+		found := false
+		for _, o := range outputs {
+			if o == expectedApkPath {
+				found = true
+				break
+			}
+		}
+		if !found {
+			t.Errorf("Can't find %q in output files.\nAll outputs:%v", expectedApkPath, outputs)
+		}
+
+		// Check if the overrides field values are correctly aggregated.
+		mod := variant.Module().(*RuntimeResourceOverlay)
+		if !reflect.DeepEqual(expected.overrides, mod.properties.Overrides) {
+			t.Errorf("Incorrect overrides property value, expected: %q, got: %q",
+				expected.overrides, mod.properties.Overrides)
+		}
+
+		// Check aapt2 flags.
+		res := variant.Output("package-res.apk")
+		aapt2Flags := res.Args["flags"]
+		checkAapt2LinkFlag(t, aapt2Flags, "rename-manifest-package", expected.packageFlag)
+		checkAapt2LinkFlag(t, aapt2Flags, "rename-resources-package", "")
+		checkAapt2LinkFlag(t, aapt2Flags, "rename-overlay-target-package", expected.targetPackageFlag)
+	}
+}
+
+func TestEnforceRRO_propagatesToDependencies(t *testing.T) {
+	testCases := []struct {
+		name                    string
+		enforceRROTargets       []string
+		enforceRROExemptTargets []string
+		rroDirs                 map[string][]string
+	}{
+		{
+			name:                    "no RRO",
+			enforceRROTargets:       nil,
+			enforceRROExemptTargets: nil,
+			rroDirs: map[string][]string{
+				"foo": nil,
+				"bar": nil,
+			},
+		},
+		{
+			name:                    "enforce RRO on all",
+			enforceRROTargets:       []string{"*"},
+			enforceRROExemptTargets: nil,
+			rroDirs: map[string][]string{
+				"foo": {"product/vendor/blah/overlay/lib2/res"},
+				"bar": {"product/vendor/blah/overlay/lib2/res"},
+			},
+		},
+		{
+			name:                    "enforce RRO on foo",
+			enforceRROTargets:       []string{"foo"},
+			enforceRROExemptTargets: nil,
+			rroDirs: map[string][]string{
+				"foo": {"product/vendor/blah/overlay/lib2/res"},
+				"bar": {"product/vendor/blah/overlay/lib2/res"},
+			},
+		},
+		{
+			name:                    "enforce RRO on foo, bar exempted",
+			enforceRROTargets:       []string{"foo"},
+			enforceRROExemptTargets: []string{"bar"},
+			rroDirs: map[string][]string{
+				"foo": {"product/vendor/blah/overlay/lib2/res"},
+				"bar": nil,
+			},
+		},
+	}
+
+	productResourceOverlays := []string{
+		"product/vendor/blah/overlay",
+	}
+
+	fs := map[string][]byte{
+		"lib2/res/values/strings.xml":                             nil,
+		"product/vendor/blah/overlay/lib2/res/values/strings.xml": nil,
+	}
+
+	bp := `
+			android_app {
+				name: "foo",
+				sdk_version: "current",
+				resource_dirs: [],
+				static_libs: ["lib"],
+			}
+
+			android_app {
+				name: "bar",
+				sdk_version: "current",
+				resource_dirs: [],
+				static_libs: ["lib"],
+			}
+
+			android_library {
+				name: "lib",
+				sdk_version: "current",
+				resource_dirs: [],
+				static_libs: ["lib2"],
+			}
+
+			android_library {
+				name: "lib2",
+				sdk_version: "current",
+				resource_dirs: ["lib2/res"],
+			}
+		`
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			config := testAppConfig(nil, bp, fs)
+			config.TestProductVariables.ProductResourceOverlays = productResourceOverlays
+			if testCase.enforceRROTargets != nil {
+				config.TestProductVariables.EnforceRROTargets = testCase.enforceRROTargets
+			}
+			if testCase.enforceRROExemptTargets != nil {
+				config.TestProductVariables.EnforceRROExemptedTargets = testCase.enforceRROExemptTargets
+			}
+
+			ctx := testContext(config)
+			run(t, ctx, config)
+
+			modules := []string{"foo", "bar"}
+			for _, moduleName := range modules {
+				module := ctx.ModuleForTests(moduleName, "android_common")
+				mkEntries := android.AndroidMkEntriesForTest(t, config, "", module.Module())[0]
+				actualRRODirs := mkEntries.EntryMap["LOCAL_SOONG_PRODUCT_RRO_DIRS"]
+				if !reflect.DeepEqual(actualRRODirs, testCase.rroDirs[moduleName]) {
+					t.Errorf("exected %s LOCAL_SOONG_PRODUCT_RRO_DIRS entry: %v\ngot:%q",
+						moduleName, testCase.rroDirs[moduleName], actualRRODirs)
+				}
+			}
+		})
+	}
+}