Separate device and product overlays

This change adds book-keeping of whether an overlay came from
DEVICE_PACKAGE_OVERLAYS or PRODUCT_PACKAGE_OVERLAYS. This is
later used when writing the output to soong_app_prebuilt.mk, to
use either LOCAL_SOONG_[DEVICE|PRODUCT]_RRO_PACKAGES depending
on the original source.

This change is intended to be a noop on its own, but allows a
follow-up make change to customize the location of the auto-generated
RRO packages.

Bug: 127758779
Test: verify noop on presubmit targets
Change-Id: Ib24fe1d05be132c360dd6966f7c83968c9939f77
diff --git a/java/aar.go b/java/aar.go
index 1a5ea4d..ba9e187 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -27,7 +27,7 @@
 	Dependency
 	ExportPackage() android.Path
 	ExportedProguardFlagFiles() android.Paths
-	ExportedRRODirs() android.Paths
+	ExportedRRODirs() []rroDir
 	ExportedStaticPackages() android.Paths
 	ExportedManifest() android.Path
 }
@@ -75,7 +75,7 @@
 	exportPackage         android.Path
 	manifestPath          android.Path
 	proguardOptionsFile   android.Path
-	rroDirs               android.Paths
+	rroDirs               []rroDir
 	rTxt                  android.Path
 	extraAaptPackagesFile android.Path
 	isLibrary             bool
@@ -99,7 +99,7 @@
 	return a.exportPackage
 }
 
-func (a *aapt) ExportedRRODirs() android.Paths {
+func (a *aapt) ExportedRRODirs() []rroDir {
 	return a.rroDirs
 }
 
@@ -108,7 +108,7 @@
 }
 
 func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, manifestPath android.Path) (flags []string,
-	deps android.Paths, resDirs, overlayDirs []globbedResourceDir, rroDirs, resZips android.Paths) {
+	deps android.Paths, resDirs, overlayDirs []globbedResourceDir, rroDirs []rroDir, resZips android.Paths) {
 
 	hasVersionCode := false
 	hasVersionName := false
@@ -286,8 +286,8 @@
 }
 
 // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
-func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStaticLibs, staticLibManifests,
-	staticRRODirs, deps android.Paths, flags []string) {
+func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStaticLibs, staticLibManifests android.Paths,
+	staticRRODirs []rroDir, deps android.Paths, flags []string) {
 
 	var sharedLibs android.Paths
 
@@ -315,7 +315,16 @@
 				transitiveStaticLibs = append(transitiveStaticLibs, aarDep.ExportedStaticPackages()...)
 				transitiveStaticLibs = append(transitiveStaticLibs, exportPackage)
 				staticLibManifests = append(staticLibManifests, aarDep.ExportedManifest())
-				staticRRODirs = append(staticRRODirs, aarDep.ExportedRRODirs()...)
+
+			outer:
+				for _, d := range aarDep.ExportedRRODirs() {
+					for _, e := range staticRRODirs {
+						if d.path == e.path {
+							continue outer
+						}
+					}
+					staticRRODirs = append(staticRRODirs, d)
+				}
 			}
 		}
 	})
@@ -332,7 +341,6 @@
 	}
 
 	transitiveStaticLibs = android.FirstUniquePaths(transitiveStaticLibs)
-	staticRRODirs = android.FirstUniquePaths(staticRRODirs)
 
 	return transitiveStaticLibs, staticLibManifests, staticRRODirs, deps, flags
 }
@@ -482,7 +490,7 @@
 	return android.Paths{a.proguardFlags}
 }
 
-func (a *AARImport) ExportedRRODirs() android.Paths {
+func (a *AARImport) ExportedRRODirs() []rroDir {
 	return nil
 }
 
diff --git a/java/android_resources.go b/java/android_resources.go
index 44cb709..c2bc746 100644
--- a/java/android_resources.go
+++ b/java/android_resources.go
@@ -41,9 +41,22 @@
 	return ctx.GlobFiles(filepath.Join(dir.String(), "**/*"), androidResourceIgnoreFilenames)
 }
 
+type overlayType int
+
+const (
+	device overlayType = iota + 1
+	product
+)
+
+type rroDir struct {
+	path        android.Path
+	overlayType overlayType
+}
+
 type overlayGlobResult struct {
-	dir   string
-	paths android.DirectorySortedPaths
+	dir         string
+	paths       android.DirectorySortedPaths
+	overlayType overlayType
 }
 
 var overlayDataKey = android.NewOnceKey("overlayDataKey")
@@ -54,7 +67,7 @@
 }
 
 func overlayResourceGlob(ctx android.ModuleContext, dir android.Path) (res []globbedResourceDir,
-	rroDirs android.Paths) {
+	rroDirs []rroDir) {
 
 	overlayData := ctx.Config().Get(overlayDataKey).([]overlayGlobResult)
 
@@ -70,7 +83,7 @@
 			// exclusion list, ignore the overlay.  The list of ignored overlays will be
 			// passed to Make to be turned into an RRO package.
 			if rroEnabled && !ctx.Config().EnforceRROExcludedOverlay(overlayModuleDir.String()) {
-				rroDirs = append(rroDirs, overlayModuleDir)
+				rroDirs = append(rroDirs, rroDir{overlayModuleDir, data.overlayType})
 			} else {
 				res = append(res, globbedResourceDir{
 					dir:   overlayModuleDir,
@@ -91,29 +104,34 @@
 
 func (overlaySingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	var overlayData []overlayGlobResult
-	overlayDirs := ctx.Config().ResourceOverlays()
-	for i := range overlayDirs {
-		// Iterate backwards through the list of overlay directories so that the later, lower-priority
-		// directories in the list show up earlier in the command line to aapt2.
-		overlay := overlayDirs[len(overlayDirs)-1-i]
-		var result overlayGlobResult
-		result.dir = overlay
 
-		files, err := ctx.GlobWithDeps(filepath.Join(overlay, "**/*"), androidResourceIgnoreFilenames)
-		if err != nil {
-			ctx.Errorf("failed to glob resource dir %q: %s", overlay, err.Error())
-			continue
-		}
-		var paths android.Paths
-		for _, f := range files {
-			if !strings.HasSuffix(f, "/") {
-				paths = append(paths, android.PathForSource(ctx, f))
+	appendOverlayData := func(overlayDirs []string, t overlayType) {
+		for i := range overlayDirs {
+			// Iterate backwards through the list of overlay directories so that the later, lower-priority
+			// directories in the list show up earlier in the command line to aapt2.
+			overlay := overlayDirs[len(overlayDirs)-1-i]
+			var result overlayGlobResult
+			result.dir = overlay
+			result.overlayType = t
+
+			files, err := ctx.GlobWithDeps(filepath.Join(overlay, "**/*"), androidResourceIgnoreFilenames)
+			if err != nil {
+				ctx.Errorf("failed to glob resource dir %q: %s", overlay, err.Error())
+				continue
 			}
+			var paths android.Paths
+			for _, f := range files {
+				if !strings.HasSuffix(f, "/") {
+					paths = append(paths, android.PathForSource(ctx, f))
+				}
+			}
+			result.paths = android.PathsToDirectorySortedPaths(paths)
+			overlayData = append(overlayData, result)
 		}
-		result.paths = android.PathsToDirectorySortedPaths(paths)
-		overlayData = append(overlayData, result)
 	}
 
+	appendOverlayData(ctx.Config().DeviceResourceOverlays(), device)
+	appendOverlayData(ctx.Config().ProductResourceOverlays(), product)
 	ctx.Config().Once(overlayDataKey, func() interface{} {
 		return overlayData
 	})
diff --git a/java/androidmk.go b/java/androidmk.go
index 0c3b1c7..5b4f738 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -257,10 +257,24 @@
 					fmt.Fprintln(w, "LOCAL_NO_STANDARD_LIBRARIES := true")
 				}
 
-				if len(app.rroDirs) > 0 {
+				filterRRO := func(filter overlayType) android.Paths {
+					var paths android.Paths
+					for _, d := range app.rroDirs {
+						if d.overlayType == filter {
+							paths = append(paths, d.path)
+						}
+					}
 					// Reverse the order, Soong stores rroDirs in aapt2 order (low to high priority), but Make
 					// expects it in LOCAL_RESOURCE_DIRS order (high to low priority).
-					fmt.Fprintln(w, "LOCAL_SOONG_RRO_DIRS :=", strings.Join(android.ReversePaths(app.rroDirs).Strings(), " "))
+					return android.ReversePaths(paths)
+				}
+				deviceRRODirs := filterRRO(device)
+				if len(deviceRRODirs) > 0 {
+					fmt.Fprintln(w, "LOCAL_SOONG_DEVICE_RRO_DIRS :=", strings.Join(deviceRRODirs.Strings(), " "))
+				}
+				productRRODirs := filterRRO(product)
+				if len(productRRODirs) > 0 {
+					fmt.Fprintln(w, "LOCAL_SOONG_PRODUCT_RRO_DIRS :=", strings.Join(productRRODirs.Strings(), " "))
 				}
 
 				if Bool(app.appProperties.Export_package_resources) {
diff --git a/java/app_test.go b/java/app_test.go
index 3942ecd..1bad123 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -211,9 +211,11 @@
 				"foo": {
 					buildDir + "/.intermediates/lib2/android_common/package-res.apk",
 					buildDir + "/.intermediates/lib/android_common/package-res.apk",
+					buildDir + "/.intermediates/lib3/android_common/package-res.apk",
 					"foo/res/res/values/strings.xml",
 					"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
 					"device/vendor/blah/overlay/foo/res/values/strings.xml",
+					"product/vendor/blah/overlay/foo/res/values/strings.xml",
 				},
 				"bar": {
 					"device/vendor/blah/static_overlay/bar/res/values/strings.xml",
@@ -244,6 +246,7 @@
 				"foo": {
 					buildDir + "/.intermediates/lib2/android_common/package-res.apk",
 					buildDir + "/.intermediates/lib/android_common/package-res.apk",
+					buildDir + "/.intermediates/lib3/android_common/package-res.apk",
 					"foo/res/res/values/strings.xml",
 					"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
 				},
@@ -260,9 +263,10 @@
 
 			rroDirs: map[string][]string{
 				"foo": {
-					"device/vendor/blah/overlay/foo/res",
+					"device:device/vendor/blah/overlay/foo/res",
 					// Enforce RRO on "foo" could imply RRO on static dependencies, but for now it doesn't.
 					// "device/vendor/blah/overlay/lib/res",
+					"product:product/vendor/blah/overlay/foo/res",
 				},
 				"bar": nil,
 				"lib": nil,
@@ -286,6 +290,7 @@
 				"foo": {
 					buildDir + "/.intermediates/lib2/android_common/package-res.apk",
 					buildDir + "/.intermediates/lib/android_common/package-res.apk",
+					buildDir + "/.intermediates/lib3/android_common/package-res.apk",
 					"foo/res/res/values/strings.xml",
 					"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
 				},
@@ -297,21 +302,27 @@
 			},
 			rroDirs: map[string][]string{
 				"foo": {
-					"device/vendor/blah/overlay/foo/res",
-					"device/vendor/blah/overlay/lib/res",
+					"device:device/vendor/blah/overlay/foo/res",
+					"product:product/vendor/blah/overlay/foo/res",
+					// Lib dep comes after the direct deps
+					"device:device/vendor/blah/overlay/lib/res",
 				},
-				"bar": {"device/vendor/blah/overlay/bar/res"},
-				"lib": {"device/vendor/blah/overlay/lib/res"},
+				"bar": {"device:device/vendor/blah/overlay/bar/res"},
+				"lib": {"device:device/vendor/blah/overlay/lib/res"},
 			},
 		},
 	}
 
-	resourceOverlays := []string{
+	deviceResourceOverlays := []string{
 		"device/vendor/blah/overlay",
 		"device/vendor/blah/overlay2",
 		"device/vendor/blah/static_overlay",
 	}
 
+	productResourceOverlays := []string{
+		"product/vendor/blah/overlay",
+	}
+
 	fs := map[string][]byte{
 		"foo/res/res/values/strings.xml":                               nil,
 		"bar/res/res/values/strings.xml":                               nil,
@@ -323,13 +334,14 @@
 		"device/vendor/blah/static_overlay/foo/res/values/strings.xml": nil,
 		"device/vendor/blah/static_overlay/bar/res/values/strings.xml": nil,
 		"device/vendor/blah/overlay2/res/values/strings.xml":           nil,
+		"product/vendor/blah/overlay/foo/res/values/strings.xml":       nil,
 	}
 
 	bp := `
 			android_app {
 				name: "foo",
 				resource_dirs: ["foo/res"],
-				static_libs: ["lib"],
+				static_libs: ["lib", "lib3"],
 			}
 
 			android_app {
@@ -347,12 +359,19 @@
 				name: "lib2",
 				resource_dirs: ["lib2/res"],
 			}
+
+			// This library has the same resources as lib (should not lead to dupe RROs)
+			android_library {
+				name: "lib3",
+				resource_dirs: ["lib/res"]
+			}
 		`
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
 			config := testConfig(nil)
-			config.TestProductVariables.ResourceOverlays = resourceOverlays
+			config.TestProductVariables.DeviceResourceOverlays = deviceResourceOverlays
+			config.TestProductVariables.ProductResourceOverlays = productResourceOverlays
 			if testCase.enforceRROTargets != nil {
 				config.TestProductVariables.EnforceRROTargets = testCase.enforceRROTargets
 			}
@@ -389,7 +408,17 @@
 					overlayFiles = resourceListToFiles(module, overlayList.Inputs.Strings())
 				}
 
-				rroDirs = module.Module().(AndroidLibraryDependency).ExportedRRODirs().Strings()
+				for _, d := range module.Module().(AndroidLibraryDependency).ExportedRRODirs() {
+					var prefix string
+					if d.overlayType == device {
+						prefix = "device:"
+					} else if d.overlayType == product {
+						prefix = "product:"
+					} else {
+						t.Fatalf("Unexpected overlayType %d", d.overlayType)
+					}
+					rroDirs = append(rroDirs, prefix+d.path.String())
+				}
 
 				return resourceFiles, overlayFiles, rroDirs
 			}