Optionally embed NOTICE files in apks.

If embed_notices or ALWAYS_EMBED_NOTICES is set, collect NOTICE files
from all dependencies of the android_app, merge them with the app's own
one (if exists), transform it to HTML, gzip it, and put it as an asset
in the final APK output.

Bug: 135460391
Test: app_test.go
Change-Id: I52d92e2fd19b3f5f396100424665c5cc344190d8
diff --git a/java/aar.go b/java/aar.go
index 47f6e5f..fc0e493 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -17,6 +17,7 @@
 import (
 	"android/soong/android"
 	"fmt"
+	"path/filepath"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -80,6 +81,7 @@
 	rTxt                    android.Path
 	extraAaptPackagesFile   android.Path
 	mergedManifestFile      android.Path
+	noticeFile              android.OptionalPath
 	isLibrary               bool
 	useEmbeddedNativeLibs   bool
 	useEmbeddedDex          bool
@@ -154,10 +156,16 @@
 		assetFiles = append(assetFiles, androidResourceGlob(ctx, dir)...)
 	}
 
+	assetDirStrings := assetDirs.Strings()
+	if a.noticeFile.Valid() {
+		assetDirStrings = append(assetDirStrings, filepath.Dir(a.noticeFile.Path().String()))
+		assetFiles = append(assetFiles, a.noticeFile.Path())
+	}
+
 	linkFlags = append(linkFlags, "--manifest "+manifestPath.String())
 	linkDeps = append(linkDeps, manifestPath)
 
-	linkFlags = append(linkFlags, android.JoinWithPrefix(assetDirs.Strings(), "-A "))
+	linkFlags = append(linkFlags, android.JoinWithPrefix(assetDirStrings, "-A "))
 	linkDeps = append(linkDeps, assetFiles...)
 
 	// SDK version flags
diff --git a/java/app.go b/java/app.go
index cab97de..42f8bad 100644
--- a/java/app.go
+++ b/java/app.go
@@ -19,6 +19,7 @@
 import (
 	"path/filepath"
 	"reflect"
+	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -103,6 +104,10 @@
 	// Use_embedded_native_libs still selects whether they are stored uncompressed and aligned or compressed.
 	// True for android_test* modules.
 	AlwaysPackageNativeLibs bool `blueprint:"mutated"`
+
+	// If set, find and merge all NOTICE files that this module and its dependencies have and store
+	// it in the APK as an asset.
+	Embed_notices *bool
 }
 
 // android_app properties that can be overridden by override_android_app
@@ -351,6 +356,54 @@
 	return jniJarFile
 }
 
+func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext, installDir android.OutputPath) android.OptionalPath {
+	if !Bool(a.appProperties.Embed_notices) && !ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
+		return android.OptionalPath{}
+	}
+
+	// Collect NOTICE files from all dependencies.
+	seenModules := make(map[android.Module]bool)
+	noticePathSet := make(map[android.Path]bool)
+
+	ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
+		// Have we already seen this?
+		if _, ok := seenModules[child]; ok {
+			return false
+		}
+		seenModules[child] = true
+
+		// Skip host modules.
+		if child.Target().Os.Class == android.Host || child.Target().Os.Class == android.HostCross {
+			return false
+		}
+
+		path := child.(android.Module).NoticeFile()
+		if path.Valid() {
+			noticePathSet[path.Path()] = true
+		}
+		return true
+	})
+
+	// If the app has one, add it too.
+	if a.NoticeFile().Valid() {
+		noticePathSet[a.NoticeFile().Path()] = true
+	}
+
+	if len(noticePathSet) == 0 {
+		return android.OptionalPath{}
+	}
+	var noticePaths []android.Path
+	for path := range noticePathSet {
+		noticePaths = append(noticePaths, path)
+	}
+	sort.Slice(noticePaths, func(i, j int) bool {
+		return noticePaths[i].String() < noticePaths[j].String()
+	})
+	noticeFile := android.BuildNoticeOutput(ctx, installDir, a.installApkName+".apk", noticePaths)
+
+	return android.OptionalPathForPath(noticeFile)
+}
+
 // Reads and prepends a main cert from the default cert dir if it hasn't been set already, i.e. it
 // isn't a cert module reference. Also checks and enforces system cert restriction if applicable.
 func processMainCert(m android.ModuleBase, certPropValue string, certificates []Certificate, ctx android.ModuleContext) []Certificate {
@@ -391,6 +444,18 @@
 	// Check if the install APK name needs to be overridden.
 	a.installApkName = ctx.DeviceConfig().OverridePackageNameFor(a.Name())
 
+	var installDir android.OutputPath
+	if ctx.ModuleName() == "framework-res" {
+		// framework-res.apk is installed as system/framework/framework-res.apk
+		installDir = android.PathForModuleInstall(ctx, "framework")
+	} else if Bool(a.appProperties.Privileged) {
+		installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName)
+	} else {
+		installDir = android.PathForModuleInstall(ctx, "app", a.installApkName)
+	}
+
+	a.aapt.noticeFile = a.noticeBuildActions(ctx, installDir)
+
 	// Process all building blocks, from AAPT to certificates.
 	a.aaptBuildActions(ctx)
 
@@ -432,16 +497,6 @@
 	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
-		installDir = android.PathForModuleInstall(ctx, "framework")
-	} else if Bool(a.appProperties.Privileged) {
-		installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName)
-	} else {
-		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_test.go b/java/app_test.go
index 27802cd..06f48fa 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -1552,3 +1552,98 @@
 		})
 	}
 }
+
+func TestEmbedNotice(t *testing.T) {
+	ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			static_libs: ["javalib"],
+			jni_libs: ["libjni"],
+			notice: "APP_NOTICE",
+			embed_notices: true,
+		}
+
+		// No embed_notice flag
+		android_app {
+			name: "bar",
+			srcs: ["a.java"],
+			jni_libs: ["libjni"],
+			notice: "APP_NOTICE",
+		}
+
+		// No NOTICE files
+		android_app {
+			name: "baz",
+			srcs: ["a.java"],
+			embed_notices: true,
+		}
+
+		cc_library {
+			name: "libjni",
+			system_shared_libs: [],
+			stl: "none",
+			notice: "LIB_NOTICE",
+		}
+
+		java_library {
+			name: "javalib",
+			srcs: [
+				":gen",
+			],
+		}
+
+		genrule {
+			name: "gen",
+			tools: ["gentool"],
+			out: ["gen.java"],
+			notice: "GENRULE_NOTICE",
+		}
+
+		java_binary_host {
+			name: "gentool",
+			srcs: ["b.java"],
+			notice: "TOOL_NOTICE",
+		}
+	`)
+
+	// foo has NOTICE files to process, and embed_notices is true.
+	foo := ctx.ModuleForTests("foo", "android_common")
+	// verify merge notices rule.
+	mergeNotices := foo.Rule("mergeNoticesRule")
+	noticeInputs := mergeNotices.Inputs.Strings()
+	// TOOL_NOTICE should be excluded as it's a host module.
+	if len(mergeNotices.Inputs) != 3 {
+		t.Errorf("number of input notice files: expected = 3, actual = %q", noticeInputs)
+	}
+	if !inList("APP_NOTICE", noticeInputs) {
+		t.Errorf("APP_NOTICE is missing from notice files, %q", noticeInputs)
+	}
+	if !inList("LIB_NOTICE", noticeInputs) {
+		t.Errorf("LIB_NOTICE is missing from notice files, %q", noticeInputs)
+	}
+	if !inList("GENRULE_NOTICE", noticeInputs) {
+		t.Errorf("GENRULE_NOTICE is missing from notice files, %q", noticeInputs)
+	}
+	// aapt2 flags should include -A <NOTICE dir> so that its contents are put in the APK's /assets.
+	res := foo.Output("package-res.apk")
+	aapt2Flags := res.Args["flags"]
+	e := "-A " + buildDir + "/.intermediates/foo/android_common/NOTICE"
+	if !strings.Contains(aapt2Flags, e) {
+		t.Errorf("asset dir flag for NOTICE, %q is missing in aapt2 link flags, %q", e, aapt2Flags)
+	}
+
+	// bar has NOTICE files to process, but embed_notices is not set.
+	bar := ctx.ModuleForTests("bar", "android_common")
+	mergeNotices = bar.MaybeRule("mergeNoticesRule")
+	if mergeNotices.Rule != nil {
+		t.Errorf("mergeNotices shouldn't have run for bar")
+	}
+
+	// baz's embed_notice is true, but it doesn't have any NOTICE files.
+	baz := ctx.ModuleForTests("baz", "android_common")
+	mergeNotices = baz.MaybeRule("mergeNoticesRule")
+	if mergeNotices.Rule != nil {
+		t.Errorf("mergeNotices shouldn't have run for baz")
+	}
+}
diff --git a/java/java_test.go b/java/java_test.go
index 22dec07..a98ea84 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -121,6 +121,10 @@
 		"b.kt":                   nil,
 		"a.jar":                  nil,
 		"b.jar":                  nil,
+		"APP_NOTICE":             nil,
+		"GENRULE_NOTICE":         nil,
+		"LIB_NOTICE":             nil,
+		"TOOL_NOTICE":            nil,
 		"java-res/a/a":           nil,
 		"java-res/b/b":           nil,
 		"java-res2/a":            nil,