Support package_splits

Pass the package_splits list from the property to aapt2 as
--split arguments, sign the extra outputs, install them, and
add them as extra output files for SourceFileProducer.

Bug: 127921149
Test: TestAppSplits
Change-Id: Id94a53ae6a8a68ec81e98abba2fefc9c23feaa7a
diff --git a/java/aapt2.go b/java/aapt2.go
index d217b9f..bcc8e97 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -161,7 +161,7 @@
 func aapt2Link(ctx android.ModuleContext,
 	packageRes, genJar, proguardOptions, rTxt, extraPackages android.WritablePath,
 	flags []string, deps android.Paths,
-	compiledRes, compiledOverlay android.Paths) {
+	compiledRes, compiledOverlay android.Paths, splitPackages android.WritablePaths) {
 
 	genDir := android.PathForModuleGen(ctx, "aapt2", "R")
 
@@ -196,12 +196,14 @@
 		inFlags = append(inFlags, "-R", "@"+overlayFileList.String())
 	}
 
+	implicitOutputs := append(splitPackages, proguardOptions, genJar, rTxt, extraPackages)
+
 	ctx.Build(pctx, android.BuildParams{
 		Rule:            aapt2LinkRule,
 		Description:     "aapt2 link",
 		Implicits:       deps,
 		Output:          packageRes,
-		ImplicitOutputs: android.WritablePaths{proguardOptions, genJar, rTxt, extraPackages},
+		ImplicitOutputs: implicitOutputs,
 		Args: map[string]string{
 			"flags":           strings.Join(flags, " "),
 			"inFlags":         strings.Join(inFlags, " "),
diff --git a/java/aar.go b/java/aar.go
index 3f13e59..29578e2 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -83,9 +83,18 @@
 	useEmbeddedDex        bool
 	usesNonSdkApis        bool
 
+	splitNames []string
+	splits     []split
+
 	aaptProperties aaptProperties
 }
 
+type split struct {
+	name   string
+	suffix string
+	path   android.Path
+}
+
 func (a *aapt) ExportPackage() android.Path {
 	return a.exportPackage
 }
@@ -248,8 +257,23 @@
 		compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files).Paths()...)
 	}
 
+	var splitPackages android.WritablePaths
+	var splits []split
+
+	for _, s := range a.splitNames {
+		suffix := strings.Replace(s, ",", "_", -1)
+		path := android.PathForModuleOut(ctx, "package_"+suffix+".apk")
+		linkFlags = append(linkFlags, "--split", path.String()+":"+s)
+		splitPackages = append(splitPackages, path)
+		splits = append(splits, split{
+			name:   s,
+			suffix: suffix,
+			path:   path,
+		})
+	}
+
 	aapt2Link(ctx, packageRes, srcJar, proguardOptionsFile, rTxt, extraPackages,
-		linkFlags, linkDeps, compiledRes, compiledOverlay)
+		linkFlags, linkDeps, compiledRes, compiledOverlay, splitPackages)
 
 	a.aaptSrcJar = srcJar
 	a.exportPackage = packageRes
@@ -258,6 +282,7 @@
 	a.rroDirs = rroDirs
 	a.extraAaptPackagesFile = extraPackages
 	a.rTxt = rTxt
+	a.splits = splits
 }
 
 // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
@@ -564,7 +589,7 @@
 	overlayRes := append(android.Paths{flata}, transitiveStaticLibs...)
 
 	aapt2Link(ctx, a.exportPackage, srcJar, proguardOptionsFile, rTxt, a.extraAaptPackagesFile,
-		linkFlags, linkDeps, nil, overlayRes)
+		linkFlags, linkDeps, nil, overlayRes, nil)
 }
 
 var _ Dependency = (*AARImport)(nil)
diff --git a/java/androidmk.go b/java/androidmk.go
index 04b328d..533b82e 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -262,6 +262,10 @@
 				if len(app.dexpreopter.builtInstalled) > 0 {
 					fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", app.dexpreopter.builtInstalled)
 				}
+				for _, split := range app.aapt.splits {
+					install := "$(LOCAL_MODULE_PATH)/" + strings.TrimSuffix(app.installApkName, ".apk") + split.suffix + ".apk"
+					fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED +=", split.path.String()+":"+install)
+				}
 			},
 		},
 	}
diff --git a/java/app.go b/java/app.go
index 8a422fc..45e3a83 100644
--- a/java/app.go
+++ b/java/app.go
@@ -226,6 +226,8 @@
 
 	aaptLinkFlags = append(aaptLinkFlags, a.additionalAaptFlags...)
 
+	a.aapt.splitNames = a.appProperties.Package_splits
+
 	a.aapt.buildActions(ctx, sdkContext(a), aaptLinkFlags...)
 
 	// apps manifests are handled by aapt, don't let Module see them
@@ -341,19 +343,32 @@
 	CreateAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, dexJarFile, certificates)
 	a.outputFile = packageFile
 
+	for _, split := range a.aapt.splits {
+		// Sign the split APKs
+		packageFile := android.PathForModuleOut(ctx, ctx.ModuleName()+"_"+split.suffix+".apk")
+		CreateAppPackage(ctx, packageFile, split.path, nil, nil, certificates)
+		a.extraOutputFiles = append(a.extraOutputFiles, packageFile)
+	}
+
 	// Build an app bundle.
 	bundleFile := android.PathForModuleOut(ctx, "base.zip")
 	BuildBundleModule(ctx, bundleFile, a.exportPackage, jniJarFile, dexJarFile)
 	a.bundleFile = bundleFile
 
 	// Install the app package.
+	var installDir android.OutputPath
 	if ctx.ModuleName() == "framework-res" {
 		// framework-res.apk is installed as system/framework/framework-res.apk
-		ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), ctx.ModuleName()+".apk", a.outputFile)
+		installDir = android.PathForModuleInstall(ctx, "framework")
 	} else if Bool(a.appProperties.Privileged) {
-		ctx.InstallFile(android.PathForModuleInstall(ctx, "priv-app", a.installApkName), a.installApkName+".apk", a.outputFile)
+		installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName)
 	} else {
-		ctx.InstallFile(android.PathForModuleInstall(ctx, "app", a.installApkName), a.installApkName+".apk", a.outputFile)
+		installDir = android.PathForModuleInstall(ctx, "app", a.installApkName)
+	}
+
+	ctx.InstallFile(installDir, a.installApkName+".apk", a.outputFile)
+	for _, split := range a.aapt.splits {
+		ctx.InstallFile(installDir, a.installApkName+"_"+split.suffix+".apk", split.path)
 	}
 }
 
diff --git a/java/app_builder.go b/java/app_builder.go
index bc91d55..e5ccbbc 100644
--- a/java/app_builder.go
+++ b/java/app_builder.go
@@ -65,7 +65,8 @@
 func CreateAppPackage(ctx android.ModuleContext, outputFile android.WritablePath,
 	packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate) {
 
-	unsignedApk := android.PathForModuleOut(ctx, "unsigned.apk")
+	unsignedApkName := strings.TrimSuffix(outputFile.Base(), ".apk") + "-unsigned.apk"
+	unsignedApk := android.PathForModuleOut(ctx, unsignedApkName)
 
 	var inputs android.Paths
 	if dexJarFile != nil {
diff --git a/java/app_test.go b/java/app_test.go
index 1784fc3..3942ecd 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -106,6 +106,30 @@
 	}
 }
 
+func TestAppSplits(t *testing.T) {
+	ctx := testApp(t, `
+				android_app {
+					name: "foo",
+					srcs: ["a.java"],
+					package_splits: ["v4", "v7,hdpi"],
+				}`)
+
+	foo := ctx.ModuleForTests("foo", "android_common")
+
+	expectedOutputs := []string{
+		filepath.Join(buildDir, ".intermediates/foo/android_common/foo.apk"),
+		filepath.Join(buildDir, ".intermediates/foo/android_common/foo_v4.apk"),
+		filepath.Join(buildDir, ".intermediates/foo/android_common/foo_v7_hdpi.apk"),
+	}
+	for _, expectedOutput := range expectedOutputs {
+		foo.Output(expectedOutput)
+	}
+
+	if g, w := foo.Module().(*AndroidApp).Srcs().Strings(), expectedOutputs; !reflect.DeepEqual(g, w) {
+		t.Errorf("want Srcs() = %q, got %q", w, g)
+	}
+}
+
 func TestResourceDirs(t *testing.T) {
 	testCases := []struct {
 		name      string
diff --git a/java/java.go b/java/java.go
index dcd6dbe..f088d86 100644
--- a/java/java.go
+++ b/java/java.go
@@ -288,7 +288,8 @@
 	proguardDictionary android.Path
 
 	// output file of the module, which may be a classes jar or a dex jar
-	outputFile android.Path
+	outputFile       android.Path
+	extraOutputFiles android.Paths
 
 	exportAidlIncludeDirs android.Paths
 
@@ -322,7 +323,7 @@
 }
 
 func (j *Module) Srcs() android.Paths {
-	return android.Paths{j.outputFile}
+	return append(android.Paths{j.outputFile}, j.extraOutputFiles...)
 }
 
 func (j *Module) DexJarFile() android.Path {