Merge "Add option to avoid recovering with diagnostics."
diff --git a/Android.bp b/Android.bp
index 76f6798..37be36c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -224,6 +224,7 @@
         "soong",
         "soong-android",
         "soong-cc",
+        "soong-dexpreopt",
         "soong-genrule",
         "soong-java-config",
         "soong-tradefed",
@@ -238,6 +239,7 @@
         "java/app.go",
         "java/builder.go",
         "java/dex.go",
+        "java/dexpreopt.go",
         "java/droiddoc.go",
         "java/gen.go",
         "java/genrule.go",
diff --git a/android/config.go b/android/config.go
index 54c9da8..a0954b6 100644
--- a/android/config.go
+++ b/android/config.go
@@ -732,14 +732,18 @@
 	return c.productVariables.ModulesLoadedByPrivilegedModules
 }
 
-func (c *config) DefaultStripDex() bool {
-	return Bool(c.productVariables.DefaultStripDex)
-}
-
 func (c *config) DisableDexPreopt(name string) bool {
 	return Bool(c.productVariables.DisableDexPreopt) || InList(name, c.productVariables.DisableDexPreoptModules)
 }
 
+func (c *config) DexpreoptGlobalConfig() string {
+	return String(c.productVariables.DexpreoptGlobalConfig)
+}
+
+func (c *config) DexPreoptProfileDir() string {
+	return String(c.productVariables.DexPreoptProfileDir)
+}
+
 func (c *deviceConfig) Arches() []Arch {
 	var arches []Arch
 	for _, target := range c.config.Targets[Android] {
@@ -854,6 +858,18 @@
 	return c.config.productVariables.BoardPlatPrivateSepolicyDirs
 }
 
+func (c *config) SecondArchIsTranslated() bool {
+	deviceTargets := c.Targets[Android]
+	if len(deviceTargets) < 2 {
+		return false
+	}
+
+	arch := deviceTargets[0].Arch
+
+	return (arch.ArchType == X86 || arch.ArchType == X86_64) &&
+		(hasArmAbi(arch) || hasArmAndroidArch(deviceTargets))
+}
+
 func (c *config) IntegerOverflowDisabledForPath(path string) bool {
 	if c.productVariables.IntegerOverflowExcludePaths == nil {
 		return false
diff --git a/android/paths.go b/android/paths.go
index b22e3c7..20039d4 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -659,11 +659,7 @@
 	if len(paths) == 0 {
 		return OptionalPath{}
 	}
-	relPath, err := filepath.Rel(p.config.srcDir, paths[0])
-	if err != nil {
-		reportPathError(ctx, err)
-		return OptionalPath{}
-	}
+	relPath := Rel(ctx, p.config.srcDir, paths[0])
 	return OptionalPathForPath(PathForSource(ctx, relPath))
 }
 
@@ -788,13 +784,7 @@
 
 func (p ModuleSrcPath) WithSubDir(ctx ModuleContext, subdir string) ModuleSrcPath {
 	subdir = PathForModuleSrc(ctx, subdir).String()
-	var err error
-	rel, err := filepath.Rel(subdir, p.path)
-	if err != nil {
-		ctx.ModuleErrorf("source file %q is not under path %q", p.path, subdir)
-		return p
-	}
-	p.rel = rel
+	p.rel = Rel(ctx, subdir, p.path)
 	return p
 }
 
@@ -932,27 +922,7 @@
 func PathForModuleInstall(ctx ModuleInstallPathContext, pathComponents ...string) OutputPath {
 	var outPaths []string
 	if ctx.Device() {
-		var partition string
-		if ctx.InstallInData() {
-			partition = "data"
-		} else if ctx.InstallInRecovery() {
-			// the layout of recovery partion is the same as that of system partition
-			partition = "recovery/root/system"
-		} else if ctx.SocSpecific() {
-			partition = ctx.DeviceConfig().VendorPath()
-		} else if ctx.DeviceSpecific() {
-			partition = ctx.DeviceConfig().OdmPath()
-		} else if ctx.ProductSpecific() {
-			partition = ctx.DeviceConfig().ProductPath()
-		} else if ctx.ProductServicesSpecific() {
-			partition = ctx.DeviceConfig().ProductServicesPath()
-		} else {
-			partition = "system"
-		}
-
-		if ctx.InstallInSanitizerDir() {
-			partition = "data/asan/" + partition
-		}
+		partition := modulePartition(ctx)
 		outPaths = []string{"target", "product", ctx.Config().DeviceName(), partition}
 	} else {
 		switch ctx.Os() {
@@ -972,6 +942,36 @@
 	return PathForOutput(ctx, outPaths...)
 }
 
+func InstallPathToOnDevicePath(ctx PathContext, path OutputPath) string {
+	rel := Rel(ctx, PathForOutput(ctx, "target", "product", ctx.Config().DeviceName()).String(), path.String())
+
+	return "/" + rel
+}
+
+func modulePartition(ctx ModuleInstallPathContext) string {
+	var partition string
+	if ctx.InstallInData() {
+		partition = "data"
+	} else if ctx.InstallInRecovery() {
+		// the layout of recovery partion is the same as that of system partition
+		partition = "recovery/root/system"
+	} else if ctx.SocSpecific() {
+		partition = ctx.DeviceConfig().VendorPath()
+	} else if ctx.DeviceSpecific() {
+		partition = ctx.DeviceConfig().OdmPath()
+	} else if ctx.ProductSpecific() {
+		partition = ctx.DeviceConfig().ProductPath()
+	} else if ctx.ProductServicesSpecific() {
+		partition = ctx.DeviceConfig().ProductServicesPath()
+	} else {
+		partition = "system"
+	}
+	if ctx.InstallInSanitizerDir() {
+		partition = "data/asan/" + partition
+	}
+	return partition
+}
+
 // validateSafePath validates a path that we trust (may contain ninja variables).
 // Ensures that each path component does not attempt to leave its component.
 func validateSafePath(pathComponents ...string) (string, error) {
@@ -1039,3 +1039,27 @@
 
 	return p
 }
+
+// Rel performs the same function as filepath.Rel, but reports errors to a PathContext, and reports an error if
+// targetPath is not inside basePath.
+func Rel(ctx PathContext, basePath string, targetPath string) string {
+	rel, isRel := MaybeRel(ctx, basePath, targetPath)
+	if !isRel {
+		reportPathErrorf(ctx, "path %q is not under path %q", targetPath, basePath)
+		return ""
+	}
+	return rel
+}
+
+// MaybeRel performs the same function as filepath.Rel, but reports errors to a PathContext, and returns false if
+// targetPath is not inside basePath.
+func MaybeRel(ctx PathContext, basePath string, targetPath string) (string, bool) {
+	rel, err := filepath.Rel(basePath, targetPath)
+	if err != nil {
+		reportPathError(ctx, err)
+		return "", false
+	} else if rel == ".." || strings.HasPrefix(rel, "../") || strings.HasPrefix(rel, "/") {
+		return "", false
+	}
+	return rel, true
+}
diff --git a/android/variable.go b/android/variable.go
index f496008..85937e3 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -196,9 +196,10 @@
 
 	UncompressPrivAppDex             *bool    `json:",omitempty"`
 	ModulesLoadedByPrivilegedModules []string `json:",omitempty"`
-	DefaultStripDex                  *bool    `json:",omitempty"`
-	DisableDexPreopt                 *bool    `json:",omitempty"`
-	DisableDexPreoptModules          []string `json:",omitempty"`
+
+	DisableDexPreopt        *bool    `json:",omitempty"`
+	DisableDexPreoptModules []string `json:",omitempty"`
+	DexPreoptProfileDir     *string  `json:",omitempty"`
 
 	IntegerOverflowExcludePaths *[]string `json:",omitempty"`
 
@@ -257,6 +258,8 @@
 	Exclude_draft_ndk_apis *bool `json:",omitempty"`
 
 	FlattenApex *bool `json:",omitempty"`
+
+	DexpreoptGlobalConfig *string `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/cc/library.go b/cc/library.go
index 2fd423b..bd43043 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1055,6 +1055,7 @@
 					m.(*Module).Properties.HideFromMake = true
 					m.(*Module).sanitize = nil
 					m.(*Module).stl = nil
+					m.(*Module).Properties.PreventInstall = true
 				}
 			}
 		} else {
diff --git a/dexpreopt/Android.bp b/dexpreopt/Android.bp
new file mode 100644
index 0000000..b832529
--- /dev/null
+++ b/dexpreopt/Android.bp
@@ -0,0 +1,15 @@
+bootstrap_go_package {
+    name: "soong-dexpreopt",
+    pkgPath: "android/soong/dexpreopt",
+    srcs: [
+        "config.go",
+        "dexpreopt.go",
+        "script.go",
+    ],
+    testSrcs: [
+        "dexpreopt_test.go",
+    ],
+    deps: [
+        "blueprint-pathtools",
+    ],
+}
\ No newline at end of file
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
new file mode 100644
index 0000000..6a4fd4a
--- /dev/null
+++ b/dexpreopt/config.go
@@ -0,0 +1,141 @@
+// Copyright 2018 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 dexpreopt
+
+import (
+	"encoding/json"
+	"io/ioutil"
+)
+
+// GlobalConfig stores the configuration for dex preopting set by the product
+type GlobalConfig struct {
+	DefaultNoStripping bool // don't strip dex files by default
+
+	DisablePreoptModules []string // modules with preopt disabled by product-specific config
+
+	OnlyPreoptBootImageAndSystemServer bool // only preopt jars in the boot image or system server
+
+	HasSystemOther        bool     // store odex files that match PatternsOnSystemOther on the system_other partition
+	PatternsOnSystemOther []string // patterns (using '%' to denote a prefix match) to put odex on the system_other partition
+
+	DisableGenerateProfile bool // don't generate profiles
+
+	BootJars         []string // jars that form the boot image
+	SystemServerJars []string // jars that form the system server
+	SystemServerApps []string // apps that are loaded into system server
+	SpeedApps        []string // apps that should be speed optimized
+
+	PreoptFlags []string // global dex2oat flags that should be used if no module-specific dex2oat flags are specified
+
+	DefaultCompilerFilter      string // default compiler filter to pass to dex2oat, overridden by --compiler-filter= in module-specific dex2oat flags
+	SystemServerCompilerFilter string // default compiler filter to pass to dex2oat for system server jars
+
+	GenerateDMFiles bool // generate Dex Metadata files
+
+	NoDebugInfo                 bool // don't generate debug info by default
+	AlwaysSystemServerDebugInfo bool // always generate mini debug info for system server modules (overrides NoDebugInfo=true)
+	NeverSystemServerDebugInfo  bool // never generate mini debug info for system server modules (overrides NoDebugInfo=false)
+	AlwaysOtherDebugInfo        bool // always generate mini debug info for non-system server modules (overrides NoDebugInfo=true)
+	NeverOtherDebugInfo         bool // never generate mini debug info for non-system server modules (overrides NoDebugInfo=true)
+
+	MissingUsesLibraries []string // libraries that may be listed in OptionalUsesLibraries but will not be installed by the product
+
+	IsEng        bool // build is a eng variant
+	SanitizeLite bool // build is the second phase of a SANITIZE_LITE build
+
+	DefaultAppImages bool // build app images (TODO: .art files?) by default
+
+	Dex2oatXmx string // max heap size
+	Dex2oatXms string // initial heap size
+
+	EmptyDirectory string // path to an empty directory
+
+	DefaultDexPreoptImageLocation map[string]string // default boot image location for each architecture
+	CpuVariant                    map[string]string // cpu variant for each architecture
+	InstructionSetFeatures        map[string]string // instruction set for each architecture
+
+	Tools Tools // paths to tools possibly used by the generated commands
+}
+
+// Tools contains paths to tools possibly used by the generated commands.  If you add a new tool here you MUST add it
+// to the order-only dependency list in DEXPREOPT_GEN_DEPS.
+type Tools struct {
+	Profman  string
+	Dex2oat  string
+	Aapt     string
+	SoongZip string
+	Zip2zip  string
+
+	VerifyUsesLibraries string
+	ConstructContext    string
+}
+
+type ModuleConfig struct {
+	Name            string
+	DexLocation     string // dex location on device
+	BuildPath       string
+	DexPath         string
+	PreferIntegrity bool
+	UncompressedDex bool
+	HasApkLibraries bool
+	PreoptFlags     []string
+
+	ProfileClassListing  string
+	ProfileIsTextListing bool
+
+	EnforceUsesLibraries  bool
+	OptionalUsesLibraries []string
+	UsesLibraries         []string
+	LibraryPaths          map[string]string
+
+	Archs                  []string
+	DexPreoptImageLocation string
+
+	PreoptExtractedApk bool // Overrides OnlyPreoptModules
+
+	NoCreateAppImage    bool
+	ForceCreateAppImage bool
+
+	PresignedPrebuilt bool
+
+	StripInputPath  string
+	StripOutputPath string
+}
+
+func LoadGlobalConfig(path string) (GlobalConfig, error) {
+	config := GlobalConfig{}
+	err := loadConfig(path, &config)
+	return config, err
+}
+
+func LoadModuleConfig(path string) (ModuleConfig, error) {
+	config := ModuleConfig{}
+	err := loadConfig(path, &config)
+	return config, err
+}
+
+func loadConfig(path string, config interface{}) error {
+	data, err := ioutil.ReadFile(path)
+	if err != nil {
+		return err
+	}
+
+	err = json.Unmarshal(data, config)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
new file mode 100644
index 0000000..0c2df6e
--- /dev/null
+++ b/dexpreopt/dexpreopt.go
@@ -0,0 +1,541 @@
+// Copyright 2018 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.
+
+// The dexpreopt package converts a global dexpreopt config and a module dexpreopt config into rules to perform
+// dexpreopting and to strip the dex files from the APK or JAR.
+//
+// It is used in two places; in the dexpeopt_gen binary for modules defined in Make, and directly linked into Soong.
+//
+// For Make modules it is built into the dexpreopt_gen binary, which is executed as a Make rule using global config and
+// module config specified in JSON files.  The binary writes out two shell scripts, only updating them if they have
+// changed.  One script takes an APK or JAR as an input and produces a zip file containing any outputs of preopting,
+// in the location they should be on the device.  The Make build rules will unzip the zip file into $(PRODUCT_OUT) when
+// installing the APK, which will install the preopt outputs into $(PRODUCT_OUT)/system or $(PRODUCT_OUT)/system_other
+// as necessary.  The zip file may be empty if preopting was disabled for any reason.  The second script takes an APK or
+// JAR as an input and strips the dex files in it as necessary.
+//
+// The intermediate shell scripts allow changes to this package or to the global config to regenerate the shell scripts
+// but only require re-executing preopting if the script has changed.
+//
+// For Soong modules this package is linked directly into Soong and run from the java package.  It generates the same
+// commands as for make, using athe same global config JSON file used by make, but using a module config structure
+// provided by Soong.  The generated commands are then converted into Soong rule and written directly to the ninja file,
+// with no extra shell scripts involved.
+package dexpreopt
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint/pathtools"
+)
+
+const SystemPartition = "/system/"
+const SystemOtherPartition = "/system_other/"
+
+// GenerateStripRule generates a set of commands that will take an APK or JAR as an input and strip the dex files if
+// they are no longer necessary after preopting.
+func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *Rule, err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			if e, ok := r.(error); ok {
+				err = e
+				rule = nil
+			} else {
+				panic(r)
+			}
+		}
+	}()
+
+	tools := global.Tools
+
+	rule = &Rule{}
+
+	strip := shouldStripDex(module, global)
+
+	if strip {
+		// Only strips if the dex files are not already uncompressed
+		rule.Command().
+			Textf(`if (zipinfo %s '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, module.StripInputPath).
+			Tool(tools.Zip2zip).FlagWithInput("-i ", module.StripInputPath).FlagWithOutput("-o ", module.StripOutputPath).
+			FlagWithArg("-x ", `"classes*.dex"`).
+			Textf(`; else cp -f %s %s; fi`, module.StripInputPath, module.StripOutputPath)
+	} else {
+		rule.Command().Text("cp -f").Input(module.StripInputPath).Output(module.StripOutputPath)
+	}
+
+	return rule, nil
+}
+
+// GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a
+// ModuleConfig.  The produced files and their install locations will be available through rule.Installs().
+func GenerateDexpreoptRule(global GlobalConfig, module ModuleConfig) (rule *Rule, err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			if e, ok := r.(error); ok {
+				err = e
+				rule = nil
+			} else {
+				panic(r)
+			}
+		}
+	}()
+
+	rule = &Rule{}
+
+	dexpreoptDisabled := contains(global.DisablePreoptModules, module.Name)
+
+	if contains(global.BootJars, module.Name) {
+		// Don't preopt individual boot jars, they will be preopted together
+		dexpreoptDisabled = true
+	}
+
+	// If OnlyPreoptBootImageAndSystemServer=true and module is not in boot class path skip
+	// Also preopt system server jars since selinux prevents system server from loading anything from
+	// /data. If we don't do this they will need to be extracted which is not favorable for RAM usage
+	// or performance. If PreoptExtractedApk is true, we ignore the only preopt boot image options.
+	if global.OnlyPreoptBootImageAndSystemServer && !contains(global.BootJars, module.Name) &&
+		!contains(global.SystemServerJars, module.Name) && !module.PreoptExtractedApk {
+		dexpreoptDisabled = true
+	}
+
+	generateProfile := module.ProfileClassListing != "" && !global.DisableGenerateProfile
+
+	var profile string
+	if generateProfile {
+		profile = profileCommand(global, module, rule)
+	}
+
+	if !dexpreoptDisabled {
+		appImage := (generateProfile || module.ForceCreateAppImage || global.DefaultAppImages) &&
+			!module.NoCreateAppImage
+
+		generateDM := shouldGenerateDM(module, global)
+
+		for _, arch := range module.Archs {
+			imageLocation := module.DexPreoptImageLocation
+			if imageLocation == "" {
+				imageLocation = global.DefaultDexPreoptImageLocation[arch]
+			}
+			dexpreoptCommand(global, module, rule, profile, arch, imageLocation, appImage, generateDM)
+		}
+	}
+
+	return rule, nil
+}
+
+func profileCommand(global GlobalConfig, module ModuleConfig, rule *Rule) string {
+	profilePath := filepath.Join(filepath.Dir(module.BuildPath), "profile.prof")
+	profileInstalledPath := module.DexLocation + ".prof"
+
+	if !module.ProfileIsTextListing {
+		rule.Command().FlagWithOutput("touch ", profilePath)
+	}
+
+	cmd := rule.Command().
+		Text(`ANDROID_LOG_TAGS="*:e"`).
+		Tool(global.Tools.Profman)
+
+	if module.ProfileIsTextListing {
+		// The profile is a test listing of classes (used for framework jars).
+		// We need to generate the actual binary profile before being able to compile.
+		cmd.FlagWithInput("--create-profile-from=", module.ProfileClassListing)
+	} else {
+		// The profile is binary profile (used for apps). Run it through profman to
+		// ensure the profile keys match the apk.
+		cmd.
+			Flag("--copy-and-update-profile-key").
+			FlagWithInput("--profile-file=", module.ProfileClassListing)
+	}
+
+	cmd.
+		FlagWithInput("--apk=", module.DexPath).
+		Flag("--dex-location="+module.DexLocation).
+		FlagWithOutput("--reference-profile-file=", profilePath)
+
+	if !module.ProfileIsTextListing {
+		cmd.Text(fmt.Sprintf(`|| echo "Profile out of date for %s"`, module.DexPath))
+	}
+	rule.Install(profilePath, profileInstalledPath)
+
+	return profilePath
+}
+
+func dexpreoptCommand(global GlobalConfig, module ModuleConfig, rule *Rule, profile, arch, bootImageLocation string,
+	appImage, generateDM bool) {
+
+	// HACK: make soname in Soong-generated .odex files match Make.
+	base := filepath.Base(module.DexLocation)
+	if filepath.Ext(base) == ".jar" {
+		base = "javalib.jar"
+	} else if filepath.Ext(base) == ".apk" {
+		base = "package.apk"
+	}
+
+	toOdexPath := func(path string) string {
+		return filepath.Join(
+			filepath.Dir(path),
+			"oat",
+			arch,
+			pathtools.ReplaceExtension(filepath.Base(path), "odex"))
+	}
+
+	odexPath := toOdexPath(filepath.Join(filepath.Dir(module.BuildPath), base))
+	odexInstallPath := toOdexPath(module.DexLocation)
+	if odexOnSystemOther(module, global) {
+		odexInstallPath = strings.Replace(odexInstallPath, SystemPartition, SystemOtherPartition, 1)
+	}
+
+	vdexPath := pathtools.ReplaceExtension(odexPath, "vdex")
+	vdexInstallPath := pathtools.ReplaceExtension(odexInstallPath, "vdex")
+
+	// bootImageLocation is $OUT/dex_bootjars/system/framework/boot.art, but dex2oat actually reads
+	// $OUT/dex_bootjars/system/framework/arm64/boot.art
+	var bootImagePath string
+	if bootImageLocation != "" {
+		bootImagePath = filepath.Join(filepath.Dir(bootImageLocation), arch, filepath.Base(bootImageLocation))
+	}
+
+	// Lists of used and optional libraries from the build config to be verified against the manifest in the APK
+	var verifyUsesLibs []string
+	var verifyOptionalUsesLibs []string
+
+	// Lists of used and optional libraries from the build config, with optional libraries that are known to not
+	// be present in the current product removed.
+	var filteredUsesLibs []string
+	var filteredOptionalUsesLibs []string
+
+	// The class loader context using paths in the build
+	var classLoaderContextHost []string
+
+	// The class loader context using paths as they will be on the device
+	var classLoaderContextTarget []string
+
+	// Extra paths that will be appended to the class loader if the APK manifest has targetSdkVersion < 28
+	var conditionalClassLoaderContextHost []string
+	var conditionalClassLoaderContextTarget []string
+
+	if module.EnforceUsesLibraries {
+		verifyUsesLibs = copyOf(module.UsesLibraries)
+		verifyOptionalUsesLibs = copyOf(module.OptionalUsesLibraries)
+
+		filteredOptionalUsesLibs = filterOut(global.MissingUsesLibraries, module.OptionalUsesLibraries)
+		filteredUsesLibs = append(copyOf(module.UsesLibraries), filteredOptionalUsesLibs...)
+
+		// Create class loader context for dex2oat from uses libraries and filtered optional libraries
+		for _, l := range filteredUsesLibs {
+
+			classLoaderContextHost = append(classLoaderContextHost,
+				pathForLibrary(module, l))
+			classLoaderContextTarget = append(classLoaderContextTarget,
+				filepath.Join("/system/framework", l+".jar"))
+		}
+
+		const httpLegacy = "org.apache.http.legacy"
+		const httpLegacyImpl = "org.apache.http.legacy.impl"
+
+		// Fix up org.apache.http.legacy.impl since it should be org.apache.http.legacy in the manifest.
+		replace(verifyUsesLibs, httpLegacyImpl, httpLegacy)
+		replace(verifyOptionalUsesLibs, httpLegacyImpl, httpLegacy)
+
+		if !contains(verifyUsesLibs, httpLegacy) && !contains(verifyOptionalUsesLibs, httpLegacy) {
+			conditionalClassLoaderContextHost = append(conditionalClassLoaderContextHost,
+				pathForLibrary(module, httpLegacyImpl))
+			conditionalClassLoaderContextTarget = append(conditionalClassLoaderContextTarget,
+				filepath.Join("/system/framework", httpLegacyImpl+".jar"))
+		}
+	} else {
+		// Pass special class loader context to skip the classpath and collision check.
+		// This will get removed once LOCAL_USES_LIBRARIES is enforced.
+		// Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default
+		// to the &.
+		classLoaderContextHost = []string{`\&`}
+	}
+
+	rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath))
+	rule.Command().FlagWithOutput("rm -f ", odexPath)
+	// Set values in the environment of the rule.  These may be modified by construct_context.sh.
+	rule.Command().FlagWithArg("class_loader_context_arg=--class-loader-context=",
+		strings.Join(classLoaderContextHost, ":"))
+	rule.Command().Text(`stored_class_loader_context_arg=""`)
+
+	if module.EnforceUsesLibraries {
+		rule.Command().FlagWithList("stored_class_loader_context_libs=", classLoaderContextTarget, ":")
+		rule.Command().FlagWithInputList("class_loader_context=", classLoaderContextHost, ":")
+		rule.Command().Textf(`uses_library_names="%s"`, strings.Join(verifyUsesLibs, " "))
+		rule.Command().Textf(`optional_uses_library_names="%s"`, strings.Join(verifyOptionalUsesLibs, " "))
+		rule.Command().Textf(`aapt_binary="%s"`, global.Tools.Aapt)
+		rule.Command().Text("source").Tool(global.Tools.VerifyUsesLibraries).Input(module.DexPath)
+		rule.Command().Text("source").Tool(global.Tools.ConstructContext).
+			Textf(`"%s"`, strings.Join(conditionalClassLoaderContextHost, ":")).
+			Textf(`"%s"`, strings.Join(conditionalClassLoaderContextTarget, ":"))
+	}
+
+	cmd := rule.Command().
+		Text(`ANDROID_LOG_TAGS="*:e"`).
+		Tool(global.Tools.Dex2oat).
+		Flag("--avoid-storing-invocation").
+		Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatXms).
+		Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatXmx).
+		Flag("${class_loader_context_arg}").
+		Flag("${stored_class_loader_context_arg}").
+		FlagWithArg("--boot-image=", bootImageLocation).Implicit(bootImagePath).
+		FlagWithInput("--dex-file=", module.DexPath).
+		FlagWithArg("--dex-location=", module.DexLocation).
+		FlagWithOutput("--oat-file=", odexPath).ImplicitOutput(vdexPath).
+		// Pass an empty directory, dex2oat shouldn't be reading arbitrary files
+		FlagWithArg("--android-root=", global.EmptyDirectory).
+		FlagWithArg("--instruction-set=", arch).
+		FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch]).
+		FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch]).
+		Flag("--no-generate-debug-info").
+		Flag("--generate-build-id").
+		Flag("--abort-on-hard-verifier-error").
+		Flag("--force-determinism").
+		FlagWithArg("--no-inline-from=", "core-oj.jar")
+
+	var preoptFlags []string
+	if len(module.PreoptFlags) > 0 {
+		preoptFlags = module.PreoptFlags
+	} else if len(global.PreoptFlags) > 0 {
+		preoptFlags = global.PreoptFlags
+	}
+
+	if len(preoptFlags) > 0 {
+		cmd.Text(strings.Join(preoptFlags, " "))
+	}
+
+	if module.UncompressedDex {
+		cmd.FlagWithArg("--copy-dex-files=", "false")
+	}
+
+	if !anyHavePrefix(preoptFlags, "--compiler-filter=") {
+		var compilerFilter string
+		if contains(global.SystemServerJars, module.Name) {
+			// Jars of system server, use the product option if it is set, speed otherwise.
+			if global.SystemServerCompilerFilter != "" {
+				compilerFilter = global.SystemServerCompilerFilter
+			} else {
+				compilerFilter = "speed"
+			}
+		} else if contains(global.SpeedApps, module.Name) || contains(global.SystemServerApps, module.Name) {
+			// Apps loaded into system server, and apps the product default to being compiled with the
+			// 'speed' compiler filter.
+			compilerFilter = "speed"
+		} else if profile != "" {
+			// For non system server jars, use speed-profile when we have a profile.
+			compilerFilter = "speed-profile"
+		} else if global.DefaultCompilerFilter != "" {
+			compilerFilter = global.DefaultCompilerFilter
+		} else {
+			compilerFilter = "quicken"
+		}
+		cmd.FlagWithArg("--compiler-filter=", compilerFilter)
+	}
+
+	if generateDM {
+		cmd.FlagWithArg("--copy-dex-files=", "false")
+		dmPath := filepath.Join(filepath.Dir(module.BuildPath), "generated.dm")
+		dmInstalledPath := pathtools.ReplaceExtension(module.DexLocation, "dm")
+		tmpPath := filepath.Join(filepath.Dir(module.BuildPath), "primary.vdex")
+		rule.Command().Text("cp -f").Input(vdexPath).Output(tmpPath)
+		rule.Command().Tool(global.Tools.SoongZip).
+			FlagWithArg("-L", "9").
+			FlagWithOutput("-o", dmPath).
+			Flag("-j").
+			Input(tmpPath)
+		rule.Install(dmPath, dmInstalledPath)
+	}
+
+	// By default, emit debug info.
+	debugInfo := true
+	if global.NoDebugInfo {
+		// If the global setting suppresses mini-debug-info, disable it.
+		debugInfo = false
+	}
+
+	// PRODUCT_SYSTEM_SERVER_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
+	// PRODUCT_OTHER_JAVA_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
+	if contains(global.SystemServerJars, module.Name) {
+		if global.AlwaysSystemServerDebugInfo {
+			debugInfo = true
+		} else if global.NeverSystemServerDebugInfo {
+			debugInfo = false
+		}
+	} else {
+		if global.AlwaysOtherDebugInfo {
+			debugInfo = true
+		} else if global.NeverOtherDebugInfo {
+			debugInfo = false
+		}
+	}
+
+	// Never enable on eng.
+	if global.IsEng {
+		debugInfo = false
+	}
+
+	if debugInfo {
+		cmd.Flag("--generate-mini-debug-info")
+	} else {
+		cmd.Flag("--no-generate-mini-debug-info")
+	}
+
+	// Set the compiler reason to 'prebuilt' to identify the oat files produced
+	// during the build, as opposed to compiled on the device.
+	cmd.FlagWithArg("--compilation-reason=", "prebuilt")
+
+	if appImage {
+		appImagePath := pathtools.ReplaceExtension(odexPath, "art")
+		appImageInstallPath := pathtools.ReplaceExtension(odexInstallPath, "art")
+		cmd.FlagWithOutput("--app-image-file=", appImagePath).
+			FlagWithArg("--image-format=", "lz4")
+		rule.Install(appImagePath, appImageInstallPath)
+	}
+
+	if profile != "" {
+		cmd.FlagWithArg("--profile-file=", profile)
+	}
+
+	rule.Install(odexPath, odexInstallPath)
+	rule.Install(vdexPath, vdexInstallPath)
+}
+
+// Return if the dex file in the APK should be stripped.  If an APK is found to contain uncompressed dex files at
+// dex2oat time it will not be stripped even if strip=true.
+func shouldStripDex(module ModuleConfig, global GlobalConfig) bool {
+	strip := !global.DefaultNoStripping
+
+	// Don't strip modules that are not on the system partition in case the oat/vdex version in system ROM
+	// doesn't match the one in other partitions. It needs to be able to fall back to the APK for that case.
+	if !strings.HasPrefix(module.DexLocation, SystemPartition) {
+		strip = false
+	}
+
+	// system_other isn't there for an OTA, so don't strip if module is on system, and odex is on system_other.
+	if odexOnSystemOther(module, global) {
+		strip = false
+	}
+
+	if module.HasApkLibraries {
+		strip = false
+	}
+
+	// Don't strip with dex files we explicitly uncompress (dexopt will not store the dex code).
+	if module.UncompressedDex {
+		strip = false
+	}
+
+	if shouldGenerateDM(module, global) {
+		strip = false
+	}
+
+	if module.PresignedPrebuilt {
+		// Only strip out files if we can re-sign the package.
+		strip = false
+	}
+
+	return strip
+}
+
+func shouldGenerateDM(module ModuleConfig, global GlobalConfig) bool {
+	// Generating DM files only makes sense for verify, avoid doing for non verify compiler filter APKs.
+	// No reason to use a dm file if the dex is already uncompressed.
+	return global.GenerateDMFiles && !module.UncompressedDex &&
+		contains(module.PreoptFlags, "--compiler-filter=verify")
+}
+
+func odexOnSystemOther(module ModuleConfig, global GlobalConfig) bool {
+	if !global.HasSystemOther {
+		return false
+	}
+
+	if global.SanitizeLite {
+		return false
+	}
+
+	if contains(global.SpeedApps, module.Name) || contains(global.SystemServerApps, module.Name) {
+		return false
+	}
+
+	for _, f := range global.PatternsOnSystemOther {
+		if makefileMatch(filepath.Join(SystemPartition, f), module.DexLocation) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func pathForLibrary(module ModuleConfig, lib string) string {
+	path := module.LibraryPaths[lib]
+	if path == "" {
+		panic(fmt.Errorf("unknown library path for %q", lib))
+	}
+	return path
+}
+
+func makefileMatch(pattern, s string) bool {
+	percent := strings.IndexByte(pattern, '%')
+	switch percent {
+	case -1:
+		return pattern == s
+	case len(pattern) - 1:
+		return strings.HasPrefix(s, pattern[:len(pattern)-1])
+	default:
+		panic(fmt.Errorf("unsupported makefile pattern %q", pattern))
+	}
+}
+
+func contains(l []string, s string) bool {
+	for _, e := range l {
+		if e == s {
+			return true
+		}
+	}
+	return false
+}
+
+// remove all elements in a from b, returning a new slice
+func filterOut(a []string, b []string) []string {
+	var ret []string
+	for _, x := range b {
+		if !contains(a, x) {
+			ret = append(ret, x)
+		}
+	}
+	return ret
+}
+
+func replace(l []string, from, to string) {
+	for i := range l {
+		if l[i] == from {
+			l[i] = to
+		}
+	}
+}
+
+func copyOf(l []string) []string {
+	return append([]string(nil), l...)
+}
+
+func anyHavePrefix(l []string, prefix string) bool {
+	for _, x := range l {
+		if strings.HasPrefix(x, prefix) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/dexpreopt/dexpreopt_gen/Android.bp b/dexpreopt/dexpreopt_gen/Android.bp
new file mode 100644
index 0000000..0790391
--- /dev/null
+++ b/dexpreopt/dexpreopt_gen/Android.bp
@@ -0,0 +1,11 @@
+blueprint_go_binary {
+    name: "dexpreopt_gen",
+    srcs: [
+        "dexpreopt_gen.go",
+    ],
+    deps: [
+        "soong-dexpreopt",
+        "blueprint-pathtools",
+        "blueprint-proptools",
+    ],
+}
\ No newline at end of file
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
new file mode 100644
index 0000000..d541c70
--- /dev/null
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -0,0 +1,172 @@
+// Copyright 2018 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 main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+
+	"android/soong/dexpreopt"
+
+	"github.com/google/blueprint/pathtools"
+	"github.com/google/blueprint/proptools"
+)
+
+var (
+	extrasOutputPath    = flag.String("extras_zip", "", "path to output zip file of extra files")
+	dexpreoptScriptPath = flag.String("dexpreopt_script", "", "path to output dexpreopt script")
+	stripScriptPath     = flag.String("strip_script", "", "path to output strip script")
+	globalConfigPath    = flag.String("global", "", "path to global configuration file")
+	moduleConfigPath    = flag.String("module", "", "path to module configuration file")
+)
+
+func main() {
+	flag.Parse()
+
+	usage := func(err string) {
+		if err != "" {
+			fmt.Println(err)
+			flag.Usage()
+			os.Exit(1)
+		}
+	}
+
+	if flag.NArg() > 0 {
+		usage("unrecognized argument " + flag.Arg(0))
+	}
+
+	if *dexpreoptScriptPath == "" {
+		usage("path to output dexpreopt script is required")
+	}
+
+	if *stripScriptPath == "" {
+		usage("path to output strip script is required")
+	}
+
+	if *globalConfigPath == "" {
+		usage("path to global configuration file is required")
+	}
+
+	if *moduleConfigPath == "" {
+		usage("path to module configuration file is required")
+	}
+
+	globalConfig, err := dexpreopt.LoadGlobalConfig(*globalConfigPath)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error loading global config %q: %s\n", *globalConfigPath, err)
+		os.Exit(2)
+	}
+
+	moduleConfig, err := dexpreopt.LoadModuleConfig(*moduleConfigPath)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error loading module config %q: %s\n", *moduleConfigPath, err)
+		os.Exit(2)
+	}
+
+	defer func() {
+		if r := recover(); r != nil {
+			switch x := r.(type) {
+			case runtime.Error:
+				panic(x)
+			case error:
+				fmt.Fprintln(os.Stderr, "error:", r)
+				os.Exit(3)
+			default:
+				panic(x)
+			}
+		}
+	}()
+
+	writeScripts(globalConfig, moduleConfig, *dexpreoptScriptPath, *stripScriptPath, *extrasOutputPath)
+}
+
+func writeScripts(global dexpreopt.GlobalConfig, module dexpreopt.ModuleConfig,
+	dexpreoptScriptPath, stripScriptPath, extrasOutputPath string) {
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(global, module)
+	if err != nil {
+		panic(err)
+	}
+
+	installDir := filepath.Join(filepath.Dir(module.BuildPath), "dexpreopt_install")
+
+	dexpreoptRule.Command().FlagWithArg("rm -rf ", installDir)
+
+	for _, install := range dexpreoptRule.Installs() {
+		installPath := filepath.Join(installDir, install.To)
+		dexpreoptRule.Command().Text("mkdir -p").Flag(filepath.Dir(installPath))
+		dexpreoptRule.Command().Text("cp -f").Input(install.From).Output(installPath)
+	}
+	dexpreoptRule.Command().Tool(global.Tools.SoongZip).
+		FlagWithOutput("-o ", extrasOutputPath).
+		FlagWithArg("-C ", installDir).
+		FlagWithArg("-D ", installDir)
+
+	stripRule, err := dexpreopt.GenerateStripRule(global, module)
+	if err != nil {
+		panic(err)
+	}
+
+	write := func(rule *dexpreopt.Rule, file, output string) {
+		script := &bytes.Buffer{}
+		script.WriteString(scriptHeader)
+		for _, c := range rule.Commands() {
+			script.WriteString(c)
+			script.WriteString("\n\n")
+		}
+
+		depFile := &bytes.Buffer{}
+		fmt.Fprintf(depFile, "%s: \\\n", output)
+		for _, tool := range dexpreoptRule.Tools() {
+			fmt.Fprintf(depFile, "    %s \\\n", tool)
+		}
+		for _, input := range dexpreoptRule.Inputs() {
+			fmt.Fprintf(depFile, "    %s \\\n", input)
+		}
+		depFile.WriteString("\n")
+
+		fmt.Fprintf(script, `/bin/bash -c 'echo -e $0 > %s' %s\n`,
+			output+".d", proptools.ShellEscape([]string{depFile.String()})[0])
+
+		err := pathtools.WriteFileIfChanged(file, script.Bytes(), 0755)
+		if err != nil {
+			panic(err)
+		}
+	}
+
+	write(dexpreoptRule, dexpreoptScriptPath, extrasOutputPath)
+	write(stripRule, stripScriptPath, module.StripOutputPath)
+}
+
+const scriptHeader = `#!/bin/bash
+
+err() {
+  errno=$?
+  echo "error: $0:$1 exited with status $errno" >&2
+  echo "error in command:" >&2
+  sed -n -e "$1p" $0 >&2
+  if [ "$errno" -ne 0 ]; then
+    exit $errno
+  else
+    exit 1
+  fi
+}
+
+trap 'err $LINENO' ERR
+
+`
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
new file mode 100644
index 0000000..5265248
--- /dev/null
+++ b/dexpreopt/dexpreopt_test.go
@@ -0,0 +1,208 @@
+// Copyright 2018 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 dexpreopt
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+)
+
+var testGlobalConfig = GlobalConfig{
+	DefaultNoStripping:                 false,
+	DisablePreoptModules:               nil,
+	OnlyPreoptBootImageAndSystemServer: false,
+	HasSystemOther:                     false,
+	PatternsOnSystemOther:              nil,
+	DisableGenerateProfile:             false,
+	BootJars:                           nil,
+	SystemServerJars:                   nil,
+	SystemServerApps:                   nil,
+	SpeedApps:                          nil,
+	PreoptFlags:                        nil,
+	DefaultCompilerFilter:              "",
+	SystemServerCompilerFilter:         "",
+	GenerateDMFiles:                    false,
+	NoDebugInfo:                        false,
+	AlwaysSystemServerDebugInfo:        false,
+	NeverSystemServerDebugInfo:         false,
+	AlwaysOtherDebugInfo:               false,
+	NeverOtherDebugInfo:                false,
+	MissingUsesLibraries:               nil,
+	IsEng:                              false,
+	SanitizeLite:                       false,
+	DefaultAppImages:                   false,
+	Dex2oatXmx:                         "",
+	Dex2oatXms:                         "",
+	EmptyDirectory:                     "",
+	DefaultDexPreoptImageLocation:      nil,
+	CpuVariant:                         nil,
+	InstructionSetFeatures:             nil,
+	Tools: Tools{
+		Profman:             "profman",
+		Dex2oat:             "dex2oat",
+		Aapt:                "aapt",
+		SoongZip:            "soong_zip",
+		Zip2zip:             "zip2zip",
+		VerifyUsesLibraries: "verify_uses_libraries.sh",
+		ConstructContext:    "construct_context.sh",
+	},
+}
+
+var testModuleConfig = ModuleConfig{
+	Name:                   "",
+	DexLocation:            "",
+	BuildPath:              "",
+	DexPath:                "",
+	PreferIntegrity:        false,
+	UncompressedDex:        false,
+	HasApkLibraries:        false,
+	PreoptFlags:            nil,
+	ProfileClassListing:    "",
+	ProfileIsTextListing:   false,
+	EnforceUsesLibraries:   false,
+	OptionalUsesLibraries:  nil,
+	UsesLibraries:          nil,
+	LibraryPaths:           nil,
+	Archs:                  nil,
+	DexPreoptImageLocation: "",
+	PreoptExtractedApk:     false,
+	NoCreateAppImage:       false,
+	ForceCreateAppImage:    false,
+	PresignedPrebuilt:      false,
+	StripInputPath:         "",
+	StripOutputPath:        "",
+}
+
+func TestDexPreopt(t *testing.T) {
+	global, module := testGlobalConfig, testModuleConfig
+
+	module.Name = "test"
+	module.DexLocation = "/system/app/test/test.apk"
+	module.BuildPath = "out/test/test.apk"
+	module.Archs = []string{"arm"}
+
+	rule, err := GenerateDexpreoptRule(global, module)
+	if err != nil {
+		t.Error(err)
+	}
+
+	wantInstalls := []Install{
+		{"out/test/oat/arm/package.odex", "/system/app/test/oat/arm/test.odex"},
+		{"out/test/oat/arm/package.vdex", "/system/app/test/oat/arm/test.vdex"},
+	}
+
+	if !reflect.DeepEqual(rule.Installs(), wantInstalls) {
+		t.Errorf("\nwant installs:\n   %v\ngot:\n   %v", wantInstalls, rule.Installs())
+	}
+}
+
+func TestDexPreoptSystemOther(t *testing.T) {
+	global, module := testGlobalConfig, testModuleConfig
+
+	global.HasSystemOther = true
+	global.PatternsOnSystemOther = []string{"app/%"}
+
+	module.Name = "test"
+	module.DexLocation = "/system/app/test/test.apk"
+	module.BuildPath = "out/test/test.apk"
+	module.Archs = []string{"arm"}
+
+	rule, err := GenerateDexpreoptRule(global, module)
+	if err != nil {
+		t.Error(err)
+	}
+
+	wantInstalls := []Install{
+		{"out/test/oat/arm/package.odex", "/system_other/app/test/oat/arm/test.odex"},
+		{"out/test/oat/arm/package.vdex", "/system_other/app/test/oat/arm/test.vdex"},
+	}
+
+	if !reflect.DeepEqual(rule.Installs(), wantInstalls) {
+		t.Errorf("\nwant installs:\n   %v\ngot:\n   %v", wantInstalls, rule.Installs())
+	}
+}
+
+func TestDexPreoptProfile(t *testing.T) {
+	global, module := testGlobalConfig, testModuleConfig
+
+	module.Name = "test"
+	module.DexLocation = "/system/app/test/test.apk"
+	module.BuildPath = "out/test/test.apk"
+	module.ProfileClassListing = "profile"
+	module.Archs = []string{"arm"}
+
+	rule, err := GenerateDexpreoptRule(global, module)
+	if err != nil {
+		t.Error(err)
+	}
+
+	wantInstalls := []Install{
+		{"out/test/profile.prof", "/system/app/test/test.apk.prof"},
+		{"out/test/oat/arm/package.art", "/system/app/test/oat/arm/test.art"},
+		{"out/test/oat/arm/package.odex", "/system/app/test/oat/arm/test.odex"},
+		{"out/test/oat/arm/package.vdex", "/system/app/test/oat/arm/test.vdex"},
+	}
+
+	if !reflect.DeepEqual(rule.Installs(), wantInstalls) {
+		t.Errorf("\nwant installs:\n   %v\ngot:\n   %v", wantInstalls, rule.Installs())
+	}
+}
+
+func TestStripDex(t *testing.T) {
+	global, module := testGlobalConfig, testModuleConfig
+
+	module.Name = "test"
+	module.DexLocation = "/system/app/test/test.apk"
+	module.BuildPath = "out/test/test.apk"
+	module.Archs = []string{"arm"}
+	module.StripInputPath = "$1"
+	module.StripOutputPath = "$2"
+
+	rule, err := GenerateStripRule(global, module)
+	if err != nil {
+		t.Error(err)
+	}
+
+	want := `zip2zip -i $1 -o $2 -x "classes*.dex"`
+	if len(rule.Commands()) < 1 || !strings.Contains(rule.Commands()[0], want) {
+		t.Errorf("\nwant commands[0] to have:\n   %v\ngot:\n   %v", want, rule.Commands()[0])
+	}
+}
+
+func TestNoStripDex(t *testing.T) {
+	global, module := testGlobalConfig, testModuleConfig
+
+	global.DefaultNoStripping = true
+
+	module.Name = "test"
+	module.DexLocation = "/system/app/test/test.apk"
+	module.BuildPath = "out/test/test.apk"
+	module.Archs = []string{"arm"}
+	module.StripInputPath = "$1"
+	module.StripOutputPath = "$2"
+
+	rule, err := GenerateStripRule(global, module)
+	if err != nil {
+		t.Error(err)
+	}
+
+	wantCommands := []string{
+		"cp -f $1 $2",
+	}
+	if !reflect.DeepEqual(rule.Commands(), wantCommands) {
+		t.Errorf("\nwant commands:\n   %v\ngot:\n   %v", wantCommands, rule.Commands())
+	}
+}
diff --git a/dexpreopt/script.go b/dexpreopt/script.go
new file mode 100644
index 0000000..fd4cf82
--- /dev/null
+++ b/dexpreopt/script.go
@@ -0,0 +1,173 @@
+// Copyright 2018 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 dexpreopt
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+type Install struct {
+	From, To string
+}
+
+type Rule struct {
+	commands []*Command
+	installs []Install
+}
+
+func (r *Rule) Install(from, to string) {
+	r.installs = append(r.installs, Install{from, to})
+}
+
+func (r *Rule) Command() *Command {
+	command := &Command{}
+	r.commands = append(r.commands, command)
+	return command
+}
+
+func (r *Rule) Inputs() []string {
+	outputs := r.outputSet()
+
+	inputs := make(map[string]bool)
+	for _, c := range r.commands {
+		for _, input := range c.inputs {
+			if !outputs[input] {
+				inputs[input] = true
+			}
+		}
+	}
+
+	var inputList []string
+	for input := range inputs {
+		inputList = append(inputList, input)
+	}
+	sort.Strings(inputList)
+
+	return inputList
+}
+
+func (r *Rule) outputSet() map[string]bool {
+	outputs := make(map[string]bool)
+	for _, c := range r.commands {
+		for _, output := range c.outputs {
+			outputs[output] = true
+		}
+	}
+	return outputs
+}
+
+func (r *Rule) Outputs() []string {
+	outputs := r.outputSet()
+
+	var outputList []string
+	for output := range outputs {
+		outputList = append(outputList, output)
+	}
+	sort.Strings(outputList)
+	return outputList
+}
+
+func (r *Rule) Installs() []Install {
+	return append([]Install(nil), r.installs...)
+}
+
+func (r *Rule) Tools() []string {
+	var tools []string
+	for _, c := range r.commands {
+		tools = append(tools, c.tools...)
+	}
+	return tools
+}
+
+func (r *Rule) Commands() []string {
+	var commands []string
+	for _, c := range r.commands {
+		commands = append(commands, string(c.buf))
+	}
+	return commands
+}
+
+type Command struct {
+	buf     []byte
+	inputs  []string
+	outputs []string
+	tools   []string
+}
+
+func (c *Command) Text(text string) *Command {
+	if len(c.buf) > 0 {
+		c.buf = append(c.buf, ' ')
+	}
+	c.buf = append(c.buf, text...)
+	return c
+}
+
+func (c *Command) Textf(format string, a ...interface{}) *Command {
+	return c.Text(fmt.Sprintf(format, a...))
+}
+
+func (c *Command) Flag(flag string) *Command {
+	return c.Text(flag)
+}
+
+func (c *Command) FlagWithArg(flag, arg string) *Command {
+	return c.Text(flag + arg)
+}
+
+func (c *Command) FlagWithList(flag string, list []string, sep string) *Command {
+	return c.Text(flag + strings.Join(list, sep))
+}
+
+func (c *Command) Tool(path string) *Command {
+	c.tools = append(c.tools, path)
+	return c.Text(path)
+}
+
+func (c *Command) Input(path string) *Command {
+	c.inputs = append(c.inputs, path)
+	return c.Text(path)
+}
+
+func (c *Command) Implicit(path string) *Command {
+	c.inputs = append(c.inputs, path)
+	return c
+}
+
+func (c *Command) Output(path string) *Command {
+	c.outputs = append(c.outputs, path)
+	return c.Text(path)
+}
+
+func (c *Command) ImplicitOutput(path string) *Command {
+	c.outputs = append(c.outputs, path)
+	return c
+}
+
+func (c *Command) FlagWithInput(flag, path string) *Command {
+	c.inputs = append(c.inputs, path)
+	return c.Text(flag + path)
+}
+
+func (c *Command) FlagWithInputList(flag string, paths []string, sep string) *Command {
+	c.inputs = append(c.inputs, paths...)
+	return c.FlagWithList(flag, paths, sep)
+}
+
+func (c *Command) FlagWithOutput(flag, path string) *Command {
+	c.outputs = append(c.outputs, path)
+	return c.Text(flag + path)
+}
diff --git a/java/aar.go b/java/aar.go
index 99e9136..89517fe 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -357,6 +357,7 @@
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
+		&module.Module.dexpreoptProperties,
 		&module.Module.protoProperties,
 		&module.aaptProperties,
 		&module.androidLibraryProperties)
diff --git a/java/androidmk.go b/java/androidmk.go
index 0700b58..70d0f7f 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -25,7 +25,7 @@
 func (library *Library) AndroidMk() android.AndroidMkData {
 	return android.AndroidMkData{
 		Class:      "JAVA_LIBRARIES",
-		OutputFile: android.OptionalPathForPath(library.implementationAndResourcesJar),
+		OutputFile: android.OptionalPathForPath(library.outputFile),
 		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 		Extra: []android.AndroidMkExtraFunc{
 			func(w io.Writer, outputFile android.Path) {
@@ -42,21 +42,12 @@
 				}
 				if library.dexJarFile != nil {
 					fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", library.dexJarFile.String())
-					if library.deviceProperties.Dex_preopt.Enabled != nil {
-						fmt.Fprintln(w, "LOCAL_DEX_PREOPT :=", *library.deviceProperties.Dex_preopt.Enabled)
-					}
-					if library.deviceProperties.Dex_preopt.App_image != nil {
-						fmt.Fprintln(w, "LOCAL_DEX_PREOPT_APP_IMAGE :=", *library.deviceProperties.Dex_preopt.App_image)
-					}
-					if library.deviceProperties.Dex_preopt.Profile_guided != nil {
-						fmt.Fprintln(w, "LOCAL_DEX_PREOPT_GENERATE_PROFILE :=", *library.deviceProperties.Dex_preopt.Profile_guided)
-					}
-					if library.deviceProperties.Dex_preopt.Profile != nil {
-						fmt.Fprintln(w, "LOCAL_DEX_PREOPT_GENERATE_PROFILE := true")
-						fmt.Fprintln(w, "LOCAL_DEX_PREOPT_PROFILE_CLASS_LISTING := $(LOCAL_PATH)/"+*library.deviceProperties.Dex_preopt.Profile)
-					}
+				}
+				if len(library.dexpreopter.builtInstalled) > 0 {
+					fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", strings.Join(library.dexpreopter.builtInstalled, " "))
 				}
 				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", library.sdkVersion())
+				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", library.implementationAndResourcesJar.String())
 				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String())
 
 				if library.jacocoReportClassesFile != nil {
@@ -84,7 +75,6 @@
 				fmt.Fprintln(w, "LOCAL_MODULE := "+name+"-hostdex")
 				fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true")
 				fmt.Fprintln(w, "LOCAL_MODULE_CLASS := JAVA_LIBRARIES")
-				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", library.implementationAndResourcesJar.String())
 				if library.installFile == nil {
 					fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
 				}
@@ -92,6 +82,7 @@
 					fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", library.dexJarFile.String())
 				}
 				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String())
+				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", library.implementationAndResourcesJar.String())
 				fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES := "+strings.Join(data.Required, " "))
 				fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_java_prebuilt.mk")
 			}
@@ -128,6 +119,7 @@
 			func(w io.Writer, outputFile android.Path) {
 				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := ", !Bool(prebuilt.properties.Installable))
 				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.combinedClasspathFile.String())
+				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.combinedClasspathFile.String())
 				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion())
 			},
 		},
@@ -142,8 +134,8 @@
 		Extra: []android.AndroidMkExtraFunc{
 			func(w io.Writer, outputFile android.Path) {
 				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
-				fmt.Fprintln(w, "LOCAL_DEX_PREOPT := false")
 				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.classpathFile.String())
+				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.classpathFile.String())
 				fmt.Fprintln(w, "LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=", prebuilt.exportPackage.String())
 				fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=", prebuilt.proguardFlags.String())
 				fmt.Fprintln(w, "LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES :=", prebuilt.extraAaptPackagesFile.String())
@@ -164,6 +156,7 @@
 			Extra: []android.AndroidMkExtraFunc{
 				func(w io.Writer, outputFile android.Path) {
 					fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", binary.headerJarFile.String())
+					fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", binary.implementationAndResourcesJar.String())
 				},
 			},
 			Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
@@ -250,6 +243,9 @@
 				for _, jniLib := range app.installJniLibs {
 					fmt.Fprintln(w, "LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), "+=", jniLib.name)
 				}
+				if len(app.dexpreopter.builtInstalled) > 0 {
+					fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", strings.Join(app.dexpreopter.builtInstalled, " "))
+				}
 			},
 		},
 	}
@@ -313,7 +309,6 @@
 		fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=",
 			strings.Join(a.exportedProguardFlagFiles.Strings(), " "))
 		fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
-		fmt.Fprintln(w, "LOCAL_DEX_PREOPT := false")
 	})
 
 	return data
diff --git a/java/app.go b/java/app.go
index 392ad3f..b44fc34 100644
--- a/java/app.go
+++ b/java/app.go
@@ -17,6 +17,7 @@
 // This file contains the module types for compiling Android apps.
 
 import (
+	"path/filepath"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -69,7 +70,6 @@
 
 	AllowDexPreopt bool `blueprint:"mutated"`
 	EmbedJNI       bool `blueprint:"mutated"`
-	StripDex       bool `blueprint:"mutated"`
 }
 
 type AndroidApp struct {
@@ -143,42 +143,16 @@
 	a.generateAndroidBuildActions(ctx)
 }
 
-// Returns whether this module should have the dex file stored uncompressed in the APK, or stripped completely.  If
-// stripped, the code will still be present on the device in the dexpreopted files.
-// This is only necessary for APKs, and not jars, because APKs are signed and the dex file should not be uncompressed
-// or removed after the signature has been generated.  For jars, which are not signed, the dex file is uncompressed
-// or removed at installation time in Make.
-func (a *AndroidApp) uncompressOrStripDex(ctx android.ModuleContext) (uncompress, strip bool) {
+// Returns whether this module should have the dex file stored uncompressed in the APK.
+func (a *AndroidApp) shouldUncompressDex(ctx android.ModuleContext) bool {
 	if ctx.Config().UnbundledBuild() {
-		return false, false
+		return false
 	}
 
-	strip = ctx.Config().DefaultStripDex()
-	// TODO(ccross): don't strip dex installed on partitions that may be updated separately (like vendor)
-	// TODO(ccross): don't strip dex on modules with LOCAL_APK_LIBRARIES equivalent
-
 	// Uncompress dex in APKs of privileged apps, and modules used by privileged apps.
-	if ctx.Config().UncompressPrivAppDex() &&
+	return ctx.Config().UncompressPrivAppDex() &&
 		(Bool(a.appProperties.Privileged) ||
-			inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules())) {
-
-		uncompress = true
-		// If the dex files is store uncompressed, don't strip it, we will reuse the uncompressed dex from the APK
-		// instead of copying it into the odex file.
-		strip = false
-	}
-
-	// If dexpreopt is disabled, don't strip the dex file
-	if !a.appProperties.AllowDexPreopt ||
-		!BoolDefault(a.deviceProperties.Dex_preopt.Enabled, true) ||
-		ctx.Config().DisableDexPreopt(ctx.ModuleName()) {
-		strip = false
-	}
-
-	// TODO(ccross): strip dexpropted modules that are not propted to system_other
-	strip = false
-
-	return uncompress, strip
+			inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules()))
 }
 
 func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
@@ -228,16 +202,25 @@
 	a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles, staticLibProguardFlagFiles...)
 	a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles, a.proguardOptionsFile)
 
-	a.deviceProperties.UncompressDex, a.appProperties.StripDex = a.uncompressOrStripDex(ctx)
+	a.deviceProperties.UncompressDex = a.shouldUncompressDex(ctx)
+
+	var installDir string
+	if ctx.ModuleName() == "framework-res" {
+		// framework-res.apk is installed as system/framework/framework-res.apk
+		installDir = "framework"
+	} else if Bool(a.appProperties.Privileged) {
+		installDir = filepath.Join("priv-app", ctx.ModuleName())
+	} else {
+		installDir = filepath.Join("app", ctx.ModuleName())
+	}
+	a.dexpreopter.installPath = android.PathForModuleInstall(ctx, installDir, ctx.ModuleName()+".apk")
+	a.dexpreopter.isPrivApp = Bool(a.appProperties.Privileged)
 
 	if ctx.ModuleName() != "framework-res" {
 		a.Module.compile(ctx, a.aaptSrcJar)
 	}
-	dexJarFile := a.dexJarFile
 
-	if a.appProperties.StripDex {
-		dexJarFile = nil
-	}
+	dexJarFile := a.maybeStrippedDexJarFile
 
 	var certificates []Certificate
 
@@ -287,9 +270,9 @@
 		// framework-res.apk is installed as system/framework/framework-res.apk
 		ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), ctx.ModuleName()+".apk", a.outputFile)
 	} else if Bool(a.appProperties.Privileged) {
-		ctx.InstallFile(android.PathForModuleInstall(ctx, "priv-app"), ctx.ModuleName()+".apk", a.outputFile)
+		ctx.InstallFile(android.PathForModuleInstall(ctx, "priv-app", ctx.ModuleName()), ctx.ModuleName()+".apk", a.outputFile)
 	} else {
-		ctx.InstallFile(android.PathForModuleInstall(ctx, "app"), ctx.ModuleName()+".apk", a.outputFile)
+		ctx.InstallFile(android.PathForModuleInstall(ctx, "app", ctx.ModuleName()), ctx.ModuleName()+".apk", a.outputFile)
 	}
 }
 
@@ -342,6 +325,7 @@
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
+		&module.Module.dexpreoptProperties,
 		&module.Module.protoProperties,
 		&module.aaptProperties,
 		&module.appProperties)
@@ -404,6 +388,7 @@
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
+		&module.Module.dexpreoptProperties,
 		&module.Module.protoProperties,
 		&module.aaptProperties,
 		&module.appProperties,
@@ -439,6 +424,7 @@
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
+		&module.Module.dexpreoptProperties,
 		&module.Module.protoProperties,
 		&module.aaptProperties,
 		&module.appProperties,
diff --git a/java/builder.go b/java/builder.go
index cefb916..8615664 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -138,6 +138,17 @@
 			CommandDeps: []string{"${config.JavaCmd}", "${config.JetifierJar}"},
 		},
 	)
+
+	zipalign = pctx.AndroidStaticRule("zipalign",
+		blueprint.RuleParams{
+			Command: "if ! ${config.ZipAlign} -c 4 $in > /dev/null; then " +
+				"${config.ZipAlign} -f 4 $in $out; " +
+				"else " +
+				"cp -f $in $out; " +
+				"fi",
+			CommandDeps: []string{"${config.ZipAlign}"},
+		},
+	)
 )
 
 func init() {
@@ -410,6 +421,15 @@
 	})
 }
 
+func TransformZipAlign(ctx android.ModuleContext, outputFile android.WritablePath, inputFile android.Path) {
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        zipalign,
+		Description: "align",
+		Input:       inputFile,
+		Output:      outputFile,
+	})
+}
+
 type classpath []android.Path
 
 func (x *classpath) FormJavaClassPath(optName string) string {
diff --git a/java/config/config.go b/java/config/config.go
index d2a8c46..da4eed7 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -126,6 +126,7 @@
 	pctx.HostJavaToolVariable("JetifierJar", "jetifier.jar")
 
 	pctx.HostBinToolVariable("SoongJavacWrapper", "soong_javac_wrapper")
+	pctx.HostBinToolVariable("DexpreoptGen", "dexpreopt_gen")
 
 	pctx.VariableFunc("JavacWrapper", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("JAVAC_WRAPPER"); override != "" {
@@ -152,4 +153,6 @@
 
 	pctx.SourcePathsVariable("ManifestMergerJars", " ", ManifestMergerClasspath...)
 	pctx.SourcePathsVariable("ManifestMergerClasspath", ":", ManifestMergerClasspath...)
+
+	pctx.HostBinToolVariable("ZipAlign", "zipalign")
 }
diff --git a/java/config/makevars.go b/java/config/makevars.go
index 275f496..01adaa7 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -65,6 +65,7 @@
 	ctx.Strict("JMOD", "${JmodCmd}")
 
 	ctx.Strict("SOONG_JAVAC_WRAPPER", "${SoongJavacWrapper}")
+	ctx.Strict("DEXPREOPT_GEN", "${DexpreoptGen}")
 	ctx.Strict("ZIPSYNC", "${ZipSyncCmd}")
 
 	ctx.Strict("JACOCO_CLI_JAR", "${JacocoCLIJar}")
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
new file mode 100644
index 0000000..3ee1205
--- /dev/null
+++ b/java/dexpreopt.go
@@ -0,0 +1,246 @@
+// Copyright 2018 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 (
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+)
+
+type dexpreopter struct {
+	dexpreoptProperties DexpreoptProperties
+
+	installPath  android.OutputPath
+	isPrivApp    bool
+	isSDKLibrary bool
+
+	builtInstalled []string
+}
+
+type DexpreoptProperties struct {
+	Dex_preopt struct {
+		// If false, prevent dexpreopting and stripping the dex file from the final jar.  Defaults to
+		// true.
+		Enabled *bool
+
+		// If true, generate an app image (.art file) for this module.
+		App_image *bool
+
+		// If true, use a checked-in profile to guide optimization.  Defaults to false unless
+		// a matching profile is set or a profile is found in PRODUCT_DEX_PREOPT_PROFILE_DIR
+		// that matches the name of this module, in which case it is defaulted to true.
+		Profile_guided *bool
+
+		// If set, provides the path to profile relative to the Android.bp file.  If not set,
+		// defaults to searching for a file that matches the name of this module in the default
+		// profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found.
+		Profile *string
+	}
+}
+
+func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool {
+	if ctx.Config().DisableDexPreopt(ctx.ModuleName()) {
+		return true
+	}
+
+	if ctx.Config().UnbundledBuild() {
+		return true
+	}
+
+	if !BoolDefault(d.dexpreoptProperties.Dex_preopt.Enabled, true) {
+		return true
+	}
+
+	// TODO: contains no java code
+	// TODO: is test
+
+	return false
+}
+
+func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.ModuleOutPath) android.ModuleOutPath {
+	if d.dexpreoptDisabled(ctx) {
+		return dexJarFile
+	}
+
+	globalConfig := ctx.Config().Once("DexpreoptGlobalConfig", func() interface{} {
+		if f := ctx.Config().DexpreoptGlobalConfig(); f != "" {
+			ctx.AddNinjaFileDeps(f)
+			globalConfig, err := dexpreopt.LoadGlobalConfig(f)
+			if err != nil {
+				panic(err)
+			}
+			return globalConfig
+		}
+		return dexpreopt.GlobalConfig{}
+	}).(dexpreopt.GlobalConfig)
+
+	var archs []string
+	for _, a := range ctx.MultiTargets() {
+		archs = append(archs, a.Arch.ArchType.String())
+	}
+	if len(archs) == 0 {
+		// assume this is a java library, dexpreopt for all arches for now
+		for _, target := range ctx.Config().Targets[android.Android] {
+			archs = append(archs, target.Arch.ArchType.String())
+		}
+		if inList(ctx.ModuleName(), globalConfig.SystemServerJars) && !d.isSDKLibrary {
+			// If the module is not an SDK library and it's a system server jar, only preopt the primary arch.
+			archs = archs[:1]
+		}
+	}
+	if ctx.Config().SecondArchIsTranslated() {
+		// Only preopt primary arch for translated arch since there is only an image there.
+		archs = archs[:1]
+	}
+
+	dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)
+
+	strippedDexJarFile := android.PathForModuleOut(ctx, "dexpreopt", dexJarFile.Base())
+
+	deps := android.Paths{dexJarFile}
+
+	var profileClassListing android.OptionalPath
+	profileIsTextListing := false
+	if BoolDefault(d.dexpreoptProperties.Dex_preopt.Profile_guided, true) {
+		// If dex_preopt.profile_guided is not set, default it based on the existence of the
+		// dexprepot.profile option or the profile class listing.
+		if String(d.dexpreoptProperties.Dex_preopt.Profile) != "" {
+			profileClassListing = android.OptionalPathForPath(
+				android.PathForModuleSrc(ctx, String(d.dexpreoptProperties.Dex_preopt.Profile)))
+			profileIsTextListing = true
+		} else {
+			profileClassListing = android.ExistentPathForSource(ctx,
+				ctx.Config().DexPreoptProfileDir(), ctx.ModuleName()+".prof")
+		}
+	}
+
+	if profileClassListing.Valid() {
+		deps = append(deps, profileClassListing.Path())
+	}
+
+	uncompressedDex := false
+	if ctx.Config().UncompressPrivAppDex() &&
+		(d.isPrivApp || inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules())) {
+		uncompressedDex = true
+	}
+
+	dexpreoptConfig := dexpreopt.ModuleConfig{
+		Name:            ctx.ModuleName(),
+		DexLocation:     dexLocation,
+		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").String(),
+		DexPath:         dexJarFile.String(),
+		PreferIntegrity: false,
+		UncompressedDex: uncompressedDex,
+		HasApkLibraries: false,
+		PreoptFlags:     nil,
+
+		ProfileClassListing:  profileClassListing.String(),
+		ProfileIsTextListing: profileIsTextListing,
+
+		EnforceUsesLibraries:  false,
+		OptionalUsesLibraries: nil,
+		UsesLibraries:         nil,
+		LibraryPaths:          nil,
+
+		Archs:                  archs,
+		DexPreoptImageLocation: "",
+
+		PreoptExtractedApk: false,
+
+		NoCreateAppImage:    !BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, true),
+		ForceCreateAppImage: BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, false),
+
+		StripInputPath:  dexJarFile.String(),
+		StripOutputPath: strippedDexJarFile.String(),
+	}
+
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(globalConfig, dexpreoptConfig)
+	if err != nil {
+		ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error())
+		return dexJarFile
+	}
+
+	var inputs android.Paths
+	for _, input := range dexpreoptRule.Inputs() {
+		if input == "" {
+			// Tests sometimes have empty configuration values that lead to empty inputs
+			continue
+		}
+		rel, isRel := android.MaybeRel(ctx, android.PathForModuleOut(ctx).String(), input)
+		if isRel {
+			inputs = append(inputs, android.PathForModuleOut(ctx, rel))
+		} else {
+			// TODO: use PathForOutput once boot image is moved to where PathForOutput can find it.
+			inputs = append(inputs, &bootImagePath{input})
+		}
+	}
+
+	var outputs android.WritablePaths
+	for _, output := range dexpreoptRule.Outputs() {
+		rel := android.Rel(ctx, android.PathForModuleOut(ctx).String(), output)
+		outputs = append(outputs, android.PathForModuleOut(ctx, rel))
+	}
+
+	for _, install := range dexpreoptRule.Installs() {
+		d.builtInstalled = append(d.builtInstalled, install.From+":"+install.To)
+	}
+
+	if len(dexpreoptRule.Commands()) > 0 {
+		ctx.Build(pctx, android.BuildParams{
+			Rule: ctx.Rule(pctx, "dexpreopt", blueprint.RuleParams{
+				Command:     strings.Join(proptools.NinjaEscape(dexpreoptRule.Commands()), " && "),
+				CommandDeps: dexpreoptRule.Tools(),
+			}),
+			Implicits:   inputs,
+			Outputs:     outputs,
+			Description: "dexpreopt",
+		})
+	}
+
+	stripRule, err := dexpreopt.GenerateStripRule(globalConfig, dexpreoptConfig)
+	if err != nil {
+		ctx.ModuleErrorf("error generating dexpreopt strip rule: %s", err.Error())
+		return dexJarFile
+	}
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule: ctx.Rule(pctx, "dexpreopt_strip", blueprint.RuleParams{
+			Command:     strings.Join(proptools.NinjaEscape(stripRule.Commands()), " && "),
+			CommandDeps: stripRule.Tools(),
+		}),
+		Input:       dexJarFile,
+		Output:      strippedDexJarFile,
+		Description: "dexpreopt strip",
+	})
+
+	return strippedDexJarFile
+}
+
+type bootImagePath struct {
+	path string
+}
+
+var _ android.Path = (*bootImagePath)(nil)
+
+func (p *bootImagePath) String() string { return p.path }
+func (p *bootImagePath) Ext() string    { return filepath.Ext(p.path) }
+func (p *bootImagePath) Base() string   { return filepath.Base(p.path) }
+func (p *bootImagePath) Rel() string    { return p.path }
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 9cde189..8e0a62a 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -626,7 +626,7 @@
 		case libTag:
 			switch dep := module.(type) {
 			case Dependency:
-				deps.classpath = append(deps.classpath, dep.ImplementationJars()...)
+				deps.classpath = append(deps.classpath, dep.HeaderJars()...)
 			case SdkLibraryDependency:
 				sdkVersion := j.sdkVersion()
 				linkType := javaSdk
diff --git a/java/java.go b/java/java.go
index 5ed99f7..aea1ef9 100644
--- a/java/java.go
+++ b/java/java.go
@@ -217,25 +217,6 @@
 	// If set to true, compile dex regardless of installable.  Defaults to false.
 	Compile_dex *bool
 
-	Dex_preopt struct {
-		// If false, prevent dexpreopting and stripping the dex file from the final jar.  Defaults to
-		// true.
-		Enabled *bool
-
-		// If true, generate an app image (.art file) for this module.
-		App_image *bool
-
-		// If true, use a checked-in profile to guide optimization.  Defaults to false unless
-		// a matching profile is set or a profile is found in PRODUCT_DEX_PREOPT_PROFILE_DIR
-		// that matches the name of this module, in which case it is defaulted to true.
-		Profile_guided *bool
-
-		// If set, provides the path to profile relative to the Android.bp file.  If not set,
-		// defaults to searching for a file that matches the name of this module in the default
-		// profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found.
-		Profile *string
-	}
-
 	Optimize struct {
 		// If false, disable all optimization.  Defaults to true for android_app and android_test
 		// modules, false for java_library and java_test modules.
@@ -266,6 +247,7 @@
 	System_modules *string
 
 	UncompressDex bool `blueprint:"mutated"`
+	IsSDKLibrary  bool `blueprint:"mutated"`
 }
 
 // Module contains the properties and members used by all java module types
@@ -295,6 +277,9 @@
 	// output file containing classes.dex and resources
 	dexJarFile android.Path
 
+	// output file that contains classes.dex if it should be in the output file
+	maybeStrippedDexJarFile android.Path
+
 	// output file containing uninstrumented classes that will be instrumented by jacoco
 	jacocoReportClassesFile android.Path
 
@@ -327,6 +312,8 @@
 	// list of source files, collected from compiledJavaSrcs and compiledSrcJars
 	// filter out Exclude_srcs, will be used by android.IDEInfo struct
 	expandIDEInfoCompiledSrcs []string
+
+	dexpreopter
 }
 
 func (j *Module) Srcs() android.Paths {
@@ -1332,7 +1319,15 @@
 
 		j.dexJarFile = dexOutputFile
 
+		dexOutputFile = j.dexpreopt(ctx, dexOutputFile)
+
+		j.maybeStrippedDexJarFile = dexOutputFile
+
 		outputFile = dexOutputFile
+
+		if ctx.Failed() {
+			return
+		}
 	} else {
 		outputFile = implementationAndResourcesJar
 	}
@@ -1486,9 +1481,17 @@
 }
 
 func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", ctx.ModuleName()+".jar")
+	j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary
 	j.compile(ctx)
 
 	if Bool(j.properties.Installable) || ctx.Host() {
+		if j.deviceProperties.UncompressDex {
+			alignedOutputFile := android.PathForModuleOut(ctx, "aligned", ctx.ModuleName()+".jar")
+			TransformZipAlign(ctx, alignedOutputFile, j.outputFile)
+			j.outputFile = alignedOutputFile
+		}
+
 		j.installFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"),
 			ctx.ModuleName()+".jar", j.outputFile)
 	}
@@ -1504,6 +1507,7 @@
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
+		&module.Module.dexpreoptProperties,
 		&module.Module.protoProperties)
 
 	InitJavaModule(module, android.HostAndDeviceSupported)
@@ -1574,6 +1578,7 @@
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
+		&module.Module.dexpreoptProperties,
 		&module.Module.protoProperties,
 		&module.testProperties)
 
@@ -1670,6 +1675,7 @@
 	module.AddProperties(
 		&module.Module.properties,
 		&module.Module.deviceProperties,
+		&module.Module.dexpreoptProperties,
 		&module.Module.protoProperties,
 		&module.binaryProperties)
 
@@ -1889,6 +1895,7 @@
 	module.AddProperties(
 		&CompilerProperties{},
 		&CompilerDeviceProperties{},
+		&DexpreoptProperties{},
 		&android.ProtoProperties{},
 		&aaptProperties{},
 		&androidLibraryProperties{},
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 573fc8e..877abe4 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -141,8 +141,9 @@
 	android.ModuleBase
 	android.DefaultableModuleBase
 
-	properties       sdkLibraryProperties
-	deviceProperties CompilerDeviceProperties
+	properties          sdkLibraryProperties
+	deviceProperties    CompilerDeviceProperties
+	dexpreoptProperties DexpreoptProperties
 
 	publicApiStubsPath android.Paths
 	systemApiStubsPath android.Paths
@@ -541,11 +542,6 @@
 			props.Srcs_lib_whitelist_pkgs = []string{"android.annotation"}
 		}
 	}
-	// These libs are required by doclava to parse the framework sources add via
-	// Src_lib and Src_lib_whitelist_* properties just above.
-	// If we don't add them to the classpath, errors messages are generated by doclava,
-	// though they don't break the build.
-	props.Libs = append(props.Libs, "framework")
 
 	if Bool(module.properties.Metalava_enabled) == true {
 		mctx.CreateModule(android.ModuleFactoryAdaptor(DroidstubsFactory), &props)
@@ -569,6 +565,7 @@
 		Errorprone       struct {
 			Javacflags []string
 		}
+		IsSDKLibrary bool
 	}{}
 
 	props.Name = proptools.StringPtr(module.implName())
@@ -579,6 +576,7 @@
 	// XML file is installed along with the impl lib
 	props.Required = []string{module.xmlFileName()}
 	props.Errorprone.Javacflags = module.properties.Errorprone.Javacflags
+	props.IsSDKLibrary = true
 
 	if module.SocSpecific() {
 		props.Soc_specific = proptools.BoolPtr(true)
@@ -588,7 +586,10 @@
 		props.Product_specific = proptools.BoolPtr(true)
 	}
 
-	mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory), &props, &module.deviceProperties)
+	mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory),
+		&props,
+		&module.deviceProperties,
+		&module.dexpreoptProperties)
 }
 
 // Creates the xml file that publicizes the runtime library
@@ -721,6 +722,7 @@
 	module := &sdkLibrary{}
 	module.AddProperties(&module.properties)
 	module.AddProperties(&module.deviceProperties)
+	module.AddProperties(&module.dexpreoptProperties)
 	InitJavaModule(module, android.DeviceSupported)
 	return module
 }