// Copyright 2017 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 (
	"fmt"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/google/blueprint/proptools"

	"android/soong/android"
	"android/soong/cc"
	"android/soong/dexpreopt"
)

// testApp runs tests using the prepareForJavaTest
//
// See testJava for an explanation as to how to stop using this deprecated method.
//
// deprecated
func testApp(t *testing.T, bp string) *android.TestContext {
	t.Helper()
	result := prepareForJavaTest.RunTestWithBp(t, bp)
	return result.TestContext
}

func TestApp(t *testing.T) {
	resourceFiles := []string{
		"res/layout/layout.xml",
		"res/values/strings.xml",
		"res/values-en-rUS/strings.xml",
	}

	compiledResourceFiles := []string{
		"aapt2/res/layout_layout.xml.flat",
		"aapt2/res/values_strings.arsc.flat",
		"aapt2/res/values-en-rUS_strings.arsc.flat",
	}

	for _, moduleType := range []string{"android_app", "android_library"} {
		t.Run(moduleType, func(t *testing.T) {
			result := android.GroupFixturePreparers(
				prepareForJavaTest,
				android.FixtureModifyMockFS(func(fs android.MockFS) {
					for _, file := range resourceFiles {
						fs[file] = nil
					}
				}),
			).RunTestWithBp(t, moduleType+` {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current"
				}
			`)

			foo := result.ModuleForTests("foo", "android_common")

			var expectedLinkImplicits []string

			manifestFixer := foo.Output("manifest_fixer/AndroidManifest.xml")
			expectedLinkImplicits = append(expectedLinkImplicits, manifestFixer.Output.String())

			frameworkRes := result.ModuleForTests("framework-res", "android_common")
			lineageRes := result.ModuleForTests("org.lineageos.platform-res", "android_common")
			expectedLinkImplicits = append(expectedLinkImplicits,
				frameworkRes.Output("package-res.apk").Output.String())
			expectedLinkImplicits = append(expectedLinkImplicits,
				lineageRes.Output("package-res.apk").Output.String())

			// Test the mapping from input files to compiled output file names
			compile := foo.Output(compiledResourceFiles[0])
			android.AssertDeepEquals(t, "aapt2 compile inputs", resourceFiles, compile.Inputs.Strings())

			compiledResourceOutputs := compile.Outputs.Strings()
			sort.Strings(compiledResourceOutputs)

			expectedLinkImplicits = append(expectedLinkImplicits, compiledResourceOutputs...)

			list := foo.Output("aapt2/res.list")
			expectedLinkImplicits = append(expectedLinkImplicits, list.Output.String())

			// Check that the link rule uses
			res := result.ModuleForTests("foo", "android_common").Output("package-res.apk")
			android.AssertDeepEquals(t, "aapt2 link implicits", expectedLinkImplicits, res.Implicits.Strings())
		})
	}
}

func TestAppSplits(t *testing.T) {
	ctx := testApp(t, `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					package_splits: ["v4", "v7,hdpi"],
					sdk_version: "current"
				}`)

	foo := ctx.ModuleForTests("foo", "android_common")

	expectedOutputs := []string{
		"out/soong/.intermediates/foo/android_common/foo.apk",
		"out/soong/.intermediates/foo/android_common/foo_v4.apk",
		"out/soong/.intermediates/foo/android_common/foo_v7_hdpi.apk",
	}
	for _, expectedOutput := range expectedOutputs {
		foo.Output(expectedOutput)
	}

	outputFiles := foo.OutputFiles(ctx, t, "")
	android.AssertPathsRelativeToTopEquals(t, `OutputFiles("")`, expectedOutputs, outputFiles)
}

func TestPlatformAPIs(t *testing.T) {
	testJava(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			platform_apis: true,
		}
	`)

	testJava(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
		}
	`)

	testJavaError(t, "This module has conflicting settings. sdk_version is empty, which means that this module is build against platform APIs. However platform_apis is not set to true", `
		android_app {
			name: "bar",
			srcs: ["b.java"],
		}
	`)

	testJavaError(t, "This module has conflicting settings. sdk_version is not empty, which means this module cannot use platform APIs. However platform_apis is set to true.", `
		android_app {
			name: "bar",
			srcs: ["b.java"],
			sdk_version: "system_current",
			platform_apis: true,
		}
	`)
}

func TestAndroidAppLinkType(t *testing.T) {
	testJava(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			libs: ["bar"],
			static_libs: ["baz"],
			platform_apis: true,
		}

		java_library {
			name: "bar",
			sdk_version: "current",
			srcs: ["b.java"],
		}

		android_library {
			name: "baz",
			sdk_version: "system_current",
			srcs: ["c.java"],
		}
	`)

	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			libs: ["bar"],
			sdk_version: "current",
			static_libs: ["baz"],
		}

		java_library {
			name: "bar",
			sdk_version: "current",
			srcs: ["b.java"],
		}

		android_library {
			name: "baz",
			sdk_version: "system_current",
			srcs: ["c.java"],
		}
	`)

	testJava(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			libs: ["bar"],
			sdk_version: "system_current",
			static_libs: ["baz"],
		}

		java_library {
			name: "bar",
			sdk_version: "current",
			srcs: ["b.java"],
		}

		android_library {
			name: "baz",
			sdk_version: "system_current",
			srcs: ["c.java"],
		}
	`)

	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			libs: ["bar"],
			sdk_version: "system_current",
			static_libs: ["baz"],
		}

		java_library {
			name: "bar",
			sdk_version: "current",
			srcs: ["b.java"],
		}

		android_library {
			name: "baz",
			srcs: ["c.java"],
		}
	`)
}

func TestUpdatableApps(t *testing.T) {
	testCases := []struct {
		name          string
		bp            string
		expectedError string
	}{
		{
			name: "Stable public SDK",
			bp: `android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "29",
					min_sdk_version: "29",
					updatable: true,
				}`,
		},
		{
			name: "Stable system SDK",
			bp: `android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "system_29",
					min_sdk_version: "29",
					updatable: true,
				}`,
		},
		{
			name: "Current public SDK",
			bp: `android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
					min_sdk_version: "29",
					updatable: true,
				}`,
		},
		{
			name: "Current system SDK",
			bp: `android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "system_current",
					min_sdk_version: "29",
					updatable: true,
				}`,
		},
		{
			name: "Current module SDK",
			bp: `android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "module_current",
					min_sdk_version: "29",
					updatable: true,
				}`,
		},
		{
			name: "Current core SDK",
			bp: `android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "core_current",
					min_sdk_version: "29",
					updatable: true,
				}`,
		},
		{
			name: "No Platform APIs",
			bp: `android_app {
					name: "foo",
					srcs: ["a.java"],
					platform_apis: true,
					min_sdk_version: "29",
					updatable: true,
				}`,
			expectedError: "Updatable apps must use stable SDKs",
		},
		{
			name: "No Core Platform APIs",
			bp: `android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "core_platform",
					min_sdk_version: "29",
					updatable: true,
				}`,
			expectedError: "Updatable apps must use stable SDKs",
		},
		{
			name: "No unspecified APIs",
			bp: `android_app {
					name: "foo",
					srcs: ["a.java"],
					updatable: true,
					min_sdk_version: "29",
				}`,
			expectedError: "Updatable apps must use stable SDK",
		},
		{
			name: "Must specify min_sdk_version",
			bp: `android_app {
					name: "app_without_min_sdk_version",
					srcs: ["a.java"],
					sdk_version: "29",
					updatable: true,
				}`,
			expectedError: "updatable apps must set min_sdk_version.",
		},
	}

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			errorHandler := android.FixtureExpectsNoErrors
			if test.expectedError != "" {
				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
			}
			android.GroupFixturePreparers(
				prepareForJavaTest, FixtureWithPrebuiltApis(map[string][]string{
					"29": {"foo"},
				})).
				ExtendWithErrorHandler(errorHandler).RunTestWithBp(t, test.bp)
		})
	}
}

func TestUpdatableApps_TransitiveDepsShouldSetMinSdkVersion(t *testing.T) {
	testJavaError(t, `module "bar".*: should support min_sdk_version\(29\)`, cc.GatherRequiredDepsForTest(android.Android)+`
		android_app {
			name: "foo",
			srcs: ["a.java"],
			updatable: true,
			sdk_version: "current",
			min_sdk_version: "29",
			static_libs: ["bar"],
		}

		java_library {
			name: "bar",
			sdk_version: "current",
		}
	`)
}

func TestUpdatableApps_JniLibsShouldShouldSupportMinSdkVersion(t *testing.T) {
	testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
		android_app {
			name: "foo",
			srcs: ["a.java"],
			updatable: true,
			sdk_version: "current",
			min_sdk_version: "current",
			jni_libs: ["libjni"],
		}

		cc_library {
			name: "libjni",
			stl: "none",
			system_shared_libs: [],
			sdk_version: "current",
		}
	`)
}

func TestUpdatableApps_JniLibShouldBeBuiltAgainstMinSdkVersion(t *testing.T) {
	bp := cc.GatherRequiredDepsForTest(android.Android) + `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			updatable: true,
			sdk_version: "current",
			min_sdk_version: "29",
			jni_libs: ["libjni"],
		}

		cc_library {
			name: "libjni",
			stl: "none",
			system_shared_libs: [],
			sdk_version: "current",
			min_sdk_version: "29",
		}
	`
	fs := map[string][]byte{
		"prebuilts/ndk/current/platforms/android-29/arch-arm64/usr/lib/crtbegin_so.o": nil,
		"prebuilts/ndk/current/platforms/android-29/arch-arm64/usr/lib/crtend_so.o":   nil,
		"prebuilts/ndk/current/platforms/android-29/arch-arm/usr/lib/crtbegin_so.o":   nil,
		"prebuilts/ndk/current/platforms/android-29/arch-arm/usr/lib/crtend_so.o":     nil,
	}

	ctx, _ := testJavaWithFS(t, bp, fs)

	inputs := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_sdk_shared").Description("link").Implicits
	var crtbeginFound, crtendFound bool
	expectedCrtBegin := ctx.ModuleForTests("crtbegin_so",
		"android_arm64_armv8-a_sdk_29").Rule("noAddrSig").Output
	expectedCrtEnd := ctx.ModuleForTests("crtend_so",
		"android_arm64_armv8-a_sdk_29").Rule("noAddrSig").Output
	implicits := []string{}
	for _, input := range inputs {
		implicits = append(implicits, input.String())
		if strings.HasSuffix(input.String(), expectedCrtBegin.String()) {
			crtbeginFound = true
		} else if strings.HasSuffix(input.String(), expectedCrtEnd.String()) {
			crtendFound = true
		}
	}
	if !crtbeginFound {
		t.Error(fmt.Sprintf(
			"expected implicit with suffix %q, have the following implicits:\n%s",
			expectedCrtBegin, strings.Join(implicits, "\n")))
	}
	if !crtendFound {
		t.Error(fmt.Sprintf(
			"expected implicit with suffix %q, have the following implicits:\n%s",
			expectedCrtEnd, strings.Join(implicits, "\n")))
	}
}

func TestUpdatableApps_ErrorIfJniLibDoesntSupportMinSdkVersion(t *testing.T) {
	bp := cc.GatherRequiredDepsForTest(android.Android) + `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			updatable: true,
			sdk_version: "current",
			min_sdk_version: "29",  // this APK should support 29
			jni_libs: ["libjni"],
		}

		cc_library {
			name: "libjni",
			stl: "none",
			sdk_version: "current",
			min_sdk_version: "current",
		}
	`
	testJavaError(t, `"libjni" .*: min_sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp)
}

func TestUpdatableApps_ErrorIfDepMinSdkVersionIsHigher(t *testing.T) {
	bp := cc.GatherRequiredDepsForTest(android.Android) + `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			updatable: true,
			sdk_version: "current",
			min_sdk_version: "29",  // this APK should support 29
			jni_libs: ["libjni"],
		}

		cc_library {
			name: "libjni",
			stl: "none",
			shared_libs: ["libbar"],
			system_shared_libs: [],
			sdk_version: "27",
			min_sdk_version: "27",
		}

		cc_library {
			name: "libbar",
			stl: "none",
			system_shared_libs: [],
			sdk_version: "current",
			min_sdk_version: "current",
		}
	`
	testJavaError(t, `"libjni" .*: links "libbar" built against newer API version "current"`, bp)
}

func TestUpdatableApps_ApplyDefaultUpdatableModuleVersion(t *testing.T) {
	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
	).RunTestWithBp(t, `
		android_app {
			name: "com.android.foo",
			srcs: ["a.java"],
			sdk_version: "current",
			min_sdk_version: "31",
			updatable: true,
		}
	`)
	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
	android.AssertStringDoesContain(t,
		"com.android.foo: expected manifest fixer to set override-placeholder-version to RELEASE_DEFAULT_UPDATABLE_MODULE_VERSION",
		foo.BuildParams.Args["args"],
		fmt.Sprintf("--override-placeholder-version %s", testDefaultUpdatableModuleVersion),
	)
}

func TestUpdatableApps_ApplyOverrideApexManifestDefaultVersion(t *testing.T) {
	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
		android.FixtureMergeEnv(map[string]string{
			"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION": "1234",
		}),
	).RunTestWithBp(t, `
		android_app {
			name: "com.android.foo",
			srcs: ["a.java"],
			sdk_version: "current",
			min_sdk_version: "31",
			updatable: true,
		}
	`)
	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
	android.AssertStringDoesContain(t,
		"com.android.foo: expected manifest fixer to set override-placeholder-version to 1234",
		foo.BuildParams.Args["args"],
		"--override-placeholder-version 1234",
	)
}

func TestResourceDirs(t *testing.T) {
	testCases := []struct {
		name      string
		prop      string
		resources []string
	}{
		{
			name:      "no resource_dirs",
			prop:      "",
			resources: []string{"res/res/values/strings.xml"},
		},
		{
			name:      "resource_dirs",
			prop:      `resource_dirs: ["res"]`,
			resources: []string{"res/res/values/strings.xml"},
		},
		{
			name:      "empty resource_dirs",
			prop:      `resource_dirs: []`,
			resources: nil,
		},
	}

	fs := android.MockFS{
		"res/res/values/strings.xml": nil,
	}

	bp := `
			android_app {
				name: "foo",
				sdk_version: "current",
				%s
			}
		`

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			result := android.GroupFixturePreparers(
				PrepareForTestWithJavaDefaultModules,
				fs.AddToFixture(),
			).RunTestWithBp(t, fmt.Sprintf(bp, testCase.prop))

			module := result.ModuleForTests("foo", "android_common")
			resourceList := module.MaybeOutput("aapt2/res.list")

			var resources []string
			if resourceList.Rule != nil {
				for _, compiledResource := range resourceList.Inputs.Strings() {
					resources = append(resources, module.Output(compiledResource).Inputs.Strings()...)
				}
			}

			android.AssertDeepEquals(t, "resource files", testCase.resources, resources)
		})
	}
}

func TestLibraryAssets(t *testing.T) {
	bp := `
			android_app {
				name: "foo",
				sdk_version: "current",
				static_libs: ["lib1", "lib2", "lib3"],
			}

			android_library {
				name: "lib1",
				sdk_version: "current",
				asset_dirs: ["assets_a"],
			}

			android_library {
				name: "lib2",
				sdk_version: "current",
			}

			android_library {
				name: "lib3",
				sdk_version: "current",
				static_libs: ["lib4", "import"],
			}

			android_library {
				name: "lib4",
				sdk_version: "current",
				asset_dirs: ["assets_b"],
			}

			android_library {
				name: "lib5",
				sdk_version: "current",
				assets: [
					"path/to/asset_file_1",
					"path/to/asset_file_2",
				],
			}

			android_library_import {
				name: "import",
				sdk_version: "current",
				aars: ["import.aar"],
			}
		`

	testCases := []struct {
		name               string
		assetFlag          string
		assetPackages      []string
		tmpAssetDirInputs  []string
		tmpAssetDirOutputs []string
	}{
		{
			name: "foo",
			// lib1 has its own assets. lib3 doesn't have any, but lib4 and import have assets.
			assetPackages: []string{
				"out/soong/.intermediates/foo/android_common/aapt2/package-res.apk",
				"out/soong/.intermediates/lib1/android_common/assets.zip",
				"out/soong/.intermediates/lib4/android_common/assets.zip",
				"out/soong/.intermediates/import/android_common/assets.zip",
			},
		},
		{
			name:      "lib1",
			assetFlag: "-A assets_a",
		},
		{
			name: "lib2",
		},
		{
			name: "lib3",
		},
		{
			name:      "lib4",
			assetFlag: "-A assets_b",
		},
		{
			name:      "lib5",
			assetFlag: "-A out/soong/.intermediates/lib5/android_common/tmp_asset_dir",
			tmpAssetDirInputs: []string{
				"path/to/asset_file_1",
				"path/to/asset_file_2",
			},
			tmpAssetDirOutputs: []string{
				"out/soong/.intermediates/lib5/android_common/tmp_asset_dir/path/to/asset_file_1",
				"out/soong/.intermediates/lib5/android_common/tmp_asset_dir/path/to/asset_file_2",
			},
		},
	}
	ctx := testApp(t, bp)

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			m := ctx.ModuleForTests(test.name, "android_common")

			// Check asset flag in aapt2 link flags
			var aapt2link android.TestingBuildParams
			if len(test.assetPackages) > 0 {
				aapt2link = m.Output("aapt2/package-res.apk")
			} else {
				aapt2link = m.Output("package-res.apk")
			}
			aapt2link = aapt2link
			aapt2Flags := aapt2link.Args["flags"]
			if test.assetFlag != "" {
				android.AssertStringDoesContain(t, "asset flag", aapt2Flags, test.assetFlag)
			} else {
				android.AssertStringDoesNotContain(t, "aapt2 link flags", aapt2Flags, " -A ")
			}

			// Check asset merge rule.
			if len(test.assetPackages) > 0 {
				mergeAssets := m.Output("package-res.apk")
				android.AssertPathsRelativeToTopEquals(t, "mergeAssets inputs", test.assetPackages, mergeAssets.Inputs)
			}

			if len(test.tmpAssetDirInputs) > 0 {
				rule := m.Rule("tmp_asset_dir")
				inputs := rule.Implicits
				outputs := append(android.WritablePaths{rule.Output}, rule.ImplicitOutputs...).Paths()
				android.AssertPathsRelativeToTopEquals(t, "tmp_asset_dir inputs", test.tmpAssetDirInputs, inputs)
				android.AssertPathsRelativeToTopEquals(t, "tmp_asset_dir outputs", test.tmpAssetDirOutputs, outputs)
			}
		})
	}
}

func TestAppJavaResources(t *testing.T) {
	bp := `
			android_app {
				name: "foo",
				sdk_version: "current",
				java_resources: ["resources/a"],
				srcs: ["a.java"],
			}

			android_app {
				name: "bar",
				sdk_version: "current",
				java_resources: ["resources/a"],
			}
		`

	ctx := testApp(t, bp)

	foo := ctx.ModuleForTests("foo", "android_common")
	fooResources := foo.Output("res/foo.jar")
	fooDexJar := foo.Output("dex-withres/foo.jar")
	fooDexJarAligned := foo.Output("dex-withres-aligned/foo.jar")
	fooApk := foo.Rule("combineApk")

	if g, w := fooDexJar.Inputs.Strings(), fooResources.Output.String(); !android.InList(w, g) {
		t.Errorf("expected resource jar %q in foo dex jar inputs %q", w, g)
	}

	if g, w := fooDexJarAligned.Input.String(), fooDexJar.Output.String(); g != w {
		t.Errorf("expected dex jar %q in foo aligned dex jar inputs %q", w, g)
	}

	if g, w := fooApk.Inputs.Strings(), fooDexJarAligned.Output.String(); !android.InList(w, g) {
		t.Errorf("expected aligned dex jar %q in foo apk inputs %q", w, g)
	}

	bar := ctx.ModuleForTests("bar", "android_common")
	barResources := bar.Output("res/bar.jar")
	barApk := bar.Rule("combineApk")

	if g, w := barApk.Inputs.Strings(), barResources.Output.String(); !android.InList(w, g) {
		t.Errorf("expected resources jar %q in bar apk inputs %q", w, g)
	}
}

func TestAndroidResourceProcessor(t *testing.T) {
	testCases := []struct {
		name                            string
		appUsesRP                       bool
		directLibUsesRP                 bool
		transitiveLibUsesRP             bool
		sharedLibUsesRP                 bool
		sharedTransitiveStaticLibUsesRP bool
		sharedTransitiveSharedLibUsesRP bool

		dontVerifyApp bool
		appResources  []string
		appOverlays   []string
		appImports    []string
		appSrcJars    []string
		appClasspath  []string
		appCombined   []string

		dontVerifyDirect bool
		directResources  []string
		directOverlays   []string
		directImports    []string
		directSrcJars    []string
		directClasspath  []string
		directCombined   []string

		dontVerifyTransitive bool
		transitiveResources  []string
		transitiveOverlays   []string
		transitiveImports    []string
		transitiveSrcJars    []string
		transitiveClasspath  []string
		transitiveCombined   []string

		dontVerifyDirectImport bool
		directImportResources  []string
		directImportOverlays   []string
		directImportImports    []string

		dontVerifyTransitiveImport bool
		transitiveImportResources  []string
		transitiveImportOverlays   []string
		transitiveImportImports    []string

		dontVerifyShared bool
		sharedResources  []string
		sharedOverlays   []string
		sharedImports    []string
		sharedSrcJars    []string
		sharedClasspath  []string
		sharedCombined   []string
	}{
		{
			// Test with all modules set to use_resource_processor: false (except android_library_import modules,
			// which always use resource processor).
			name:                "legacy",
			appUsesRP:           false,
			directLibUsesRP:     false,
			transitiveLibUsesRP: false,

			appResources: nil,
			appOverlays: []string{
				"out/soong/.intermediates/transitive/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import/android_common/package-res.apk",
				"out/soong/.intermediates/direct/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import/android_common/package-res.apk",
				"out/soong/.intermediates/app/android_common/aapt2/app/res/values_strings.arsc.flat",
			},
			appImports: []string{
				"out/soong/.intermediates/shared/android_common/package-res.apk",
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
			},
			appSrcJars: []string{"out/soong/.intermediates/app/android_common/gen/android/R.srcjar"},
			appClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/shared/android_common/turbine-combined/shared.jar",
				"out/soong/.intermediates/direct/android_common/turbine-combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/turbine-combined/direct_import.jar",
			},
			appCombined: []string{
				"out/soong/.intermediates/app/android_common/javac/app.jar",
				"out/soong/.intermediates/direct/android_common/combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/combined/direct_import.jar",
			},

			directResources: nil,
			directOverlays: []string{
				"out/soong/.intermediates/transitive/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import/android_common/package-res.apk",
				"out/soong/.intermediates/direct/android_common/aapt2/direct/res/values_strings.arsc.flat",
			},
			directImports: []string{"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk"},
			directSrcJars: []string{"out/soong/.intermediates/direct/android_common/gen/android/R.srcjar"},
			directClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/transitive/android_common/turbine-combined/transitive.jar",
				"out/soong/.intermediates/transitive_import/android_common/turbine-combined/transitive_import.jar",
			},
			directCombined: []string{
				"out/soong/.intermediates/direct/android_common/javac/direct.jar",
				"out/soong/.intermediates/transitive/android_common/javac/transitive.jar",
				"out/soong/.intermediates/transitive_import/android_common/combined/transitive_import.jar",
			},

			transitiveResources: []string{"out/soong/.intermediates/transitive/android_common/aapt2/transitive/res/values_strings.arsc.flat"},
			transitiveOverlays:  nil,
			transitiveImports:   []string{"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk"},
			transitiveSrcJars:   []string{"out/soong/.intermediates/transitive/android_common/gen/android/R.srcjar"},
			transitiveClasspath: []string{"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar"},
			transitiveCombined:  nil,

			sharedResources: nil,
			sharedOverlays: []string{
				"out/soong/.intermediates/shared_transitive_static/android_common/package-res.apk",
				"out/soong/.intermediates/shared/android_common/aapt2/shared/res/values_strings.arsc.flat",
			},
			sharedImports: []string{
				"out/soong/.intermediates/shared_transitive_shared/android_common/package-res.apk",
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
			},
			sharedSrcJars: []string{"out/soong/.intermediates/shared/android_common/gen/android/R.srcjar"},
			sharedClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/shared_transitive_shared/android_common/turbine-combined/shared_transitive_shared.jar",
				"out/soong/.intermediates/shared_transitive_static/android_common/turbine-combined/shared_transitive_static.jar",
			},
			sharedCombined: []string{
				"out/soong/.intermediates/shared/android_common/javac/shared.jar",
				"out/soong/.intermediates/shared_transitive_static/android_common/javac/shared_transitive_static.jar",
			},

			directImportResources: nil,
			directImportOverlays:  []string{"out/soong/.intermediates/direct_import/android_common/flat-res/gen_res.flata"},
			directImportImports: []string{
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import_dep/android_common/package-res.apk",
			},

			transitiveImportResources: nil,
			transitiveImportOverlays:  []string{"out/soong/.intermediates/transitive_import/android_common/flat-res/gen_res.flata"},
			transitiveImportImports: []string{
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
			},
		},
		{
			// Test with all modules set to use_resource_processor: true.
			name:                            "resource_processor",
			appUsesRP:                       true,
			directLibUsesRP:                 true,
			transitiveLibUsesRP:             true,
			sharedLibUsesRP:                 true,
			sharedTransitiveSharedLibUsesRP: true,
			sharedTransitiveStaticLibUsesRP: true,

			appResources: nil,
			appOverlays: []string{
				"out/soong/.intermediates/transitive/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import/android_common/package-res.apk",
				"out/soong/.intermediates/direct/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import/android_common/package-res.apk",
				"out/soong/.intermediates/app/android_common/aapt2/app/res/values_strings.arsc.flat",
			},
			appImports: []string{
				"out/soong/.intermediates/shared/android_common/package-res.apk",
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
			},
			appSrcJars: nil,
			appClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/app/android_common/busybox/R.jar",
				"out/soong/.intermediates/shared/android_common/turbine-combined/shared.jar",
				"out/soong/.intermediates/direct/android_common/turbine-combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/turbine-combined/direct_import.jar",
			},
			appCombined: []string{
				"out/soong/.intermediates/app/android_common/javac/app.jar",
				"out/soong/.intermediates/app/android_common/busybox/R.jar",
				"out/soong/.intermediates/direct/android_common/combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/combined/direct_import.jar",
			},

			directResources: nil,
			directOverlays:  []string{"out/soong/.intermediates/direct/android_common/aapt2/direct/res/values_strings.arsc.flat"},
			directImports: []string{
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/transitive/android_common/package-res.apk",
			},
			directSrcJars: nil,
			directClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/direct/android_common/busybox/R.jar",
				"out/soong/.intermediates/transitive/android_common/busybox/R.jar",
				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
				"out/soong/.intermediates/transitive/android_common/turbine-combined/transitive.jar",
				"out/soong/.intermediates/transitive_import/android_common/turbine-combined/transitive_import.jar",
			},
			directCombined: []string{
				"out/soong/.intermediates/direct/android_common/javac/direct.jar",
				"out/soong/.intermediates/transitive/android_common/javac/transitive.jar",
				"out/soong/.intermediates/transitive_import/android_common/combined/transitive_import.jar",
			},

			transitiveResources: []string{"out/soong/.intermediates/transitive/android_common/aapt2/transitive/res/values_strings.arsc.flat"},
			transitiveOverlays:  nil,
			transitiveImports:   []string{"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk"},
			transitiveSrcJars:   nil,
			transitiveClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/transitive/android_common/busybox/R.jar",
			},
			transitiveCombined: nil,

			sharedResources: nil,
			sharedOverlays:  []string{"out/soong/.intermediates/shared/android_common/aapt2/shared/res/values_strings.arsc.flat"},
			sharedImports: []string{
				"out/soong/.intermediates/shared_transitive_shared/android_common/package-res.apk",
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
				"out/soong/.intermediates/shared_transitive_static/android_common/package-res.apk",
			},
			sharedSrcJars: nil,
			sharedClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/shared/android_common/busybox/R.jar",
				"out/soong/.intermediates/shared_transitive_static/android_common/busybox/R.jar",
				"out/soong/.intermediates/shared_transitive_shared/android_common/busybox/R.jar",
				"out/soong/.intermediates/shared_transitive_shared/android_common/turbine-combined/shared_transitive_shared.jar",
				"out/soong/.intermediates/shared_transitive_static/android_common/turbine-combined/shared_transitive_static.jar",
			},
			sharedCombined: []string{
				"out/soong/.intermediates/shared/android_common/javac/shared.jar",
				"out/soong/.intermediates/shared_transitive_static/android_common/javac/shared_transitive_static.jar",
			},

			directImportResources: nil,
			directImportOverlays:  []string{"out/soong/.intermediates/direct_import/android_common/flat-res/gen_res.flata"},
			directImportImports: []string{
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import_dep/android_common/package-res.apk",
			},

			transitiveImportResources: nil,
			transitiveImportOverlays:  []string{"out/soong/.intermediates/transitive_import/android_common/flat-res/gen_res.flata"},
			transitiveImportImports: []string{
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
			},
		}, {
			// Test an app building with resource processor enabled but with dependencies built without
			// resource processor.
			name:                "app_resource_processor",
			appUsesRP:           true,
			directLibUsesRP:     false,
			transitiveLibUsesRP: false,

			appResources: nil,
			appOverlays: []string{
				"out/soong/.intermediates/transitive/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import/android_common/package-res.apk",
				"out/soong/.intermediates/direct/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import/android_common/package-res.apk",
				"out/soong/.intermediates/app/android_common/aapt2/app/res/values_strings.arsc.flat",
			},
			appImports: []string{
				"out/soong/.intermediates/shared/android_common/package-res.apk",
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
			},
			appSrcJars: nil,
			appClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				// R.jar has to come before direct.jar
				"out/soong/.intermediates/app/android_common/busybox/R.jar",
				"out/soong/.intermediates/shared/android_common/turbine-combined/shared.jar",
				"out/soong/.intermediates/direct/android_common/turbine-combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/turbine-combined/direct_import.jar",
			},
			appCombined: []string{
				"out/soong/.intermediates/app/android_common/javac/app.jar",
				"out/soong/.intermediates/app/android_common/busybox/R.jar",
				"out/soong/.intermediates/direct/android_common/combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/combined/direct_import.jar",
			},

			dontVerifyDirect:           true,
			dontVerifyTransitive:       true,
			dontVerifyShared:           true,
			dontVerifyDirectImport:     true,
			dontVerifyTransitiveImport: true,
		},
		{
			// Test an app building without resource processor enabled but with a dependency built with
			// resource processor.
			name:                "app_dependency_lib_resource_processor",
			appUsesRP:           false,
			directLibUsesRP:     true,
			transitiveLibUsesRP: false,

			appOverlays: []string{
				"out/soong/.intermediates/transitive/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import/android_common/package-res.apk",
				"out/soong/.intermediates/direct/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import/android_common/package-res.apk",
				"out/soong/.intermediates/app/android_common/aapt2/app/res/values_strings.arsc.flat",
			},
			appImports: []string{
				"out/soong/.intermediates/shared/android_common/package-res.apk",
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
			},
			appSrcJars: []string{"out/soong/.intermediates/app/android_common/gen/android/R.srcjar"},
			appClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/shared/android_common/turbine-combined/shared.jar",
				"out/soong/.intermediates/direct/android_common/turbine-combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/turbine-combined/direct_import.jar",
			},
			appCombined: []string{
				"out/soong/.intermediates/app/android_common/javac/app.jar",
				"out/soong/.intermediates/direct/android_common/combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/combined/direct_import.jar",
			},

			directResources: nil,
			directOverlays:  []string{"out/soong/.intermediates/direct/android_common/aapt2/direct/res/values_strings.arsc.flat"},
			directImports: []string{
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/transitive/android_common/package-res.apk",
			},
			directSrcJars: nil,
			directClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/direct/android_common/busybox/R.jar",
				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
				"out/soong/.intermediates/transitive/android_common/turbine-combined/transitive.jar",
				"out/soong/.intermediates/transitive_import/android_common/turbine-combined/transitive_import.jar",
			},
			directCombined: []string{
				"out/soong/.intermediates/direct/android_common/javac/direct.jar",
				"out/soong/.intermediates/transitive/android_common/javac/transitive.jar",
				"out/soong/.intermediates/transitive_import/android_common/combined/transitive_import.jar",
			},

			dontVerifyTransitive:       true,
			dontVerifyShared:           true,
			dontVerifyDirectImport:     true,
			dontVerifyTransitiveImport: true,
		},
		{
			// Test a library building without resource processor enabled but with a dependency built with
			// resource processor.
			name:                "lib_dependency_lib_resource_processor",
			appUsesRP:           false,
			directLibUsesRP:     false,
			transitiveLibUsesRP: true,

			appOverlays: []string{
				"out/soong/.intermediates/transitive/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import/android_common/package-res.apk",
				"out/soong/.intermediates/direct/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/direct_import/android_common/package-res.apk",
				"out/soong/.intermediates/app/android_common/aapt2/app/res/values_strings.arsc.flat",
			},
			appImports: []string{
				"out/soong/.intermediates/shared/android_common/package-res.apk",
				"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk",
			},
			appSrcJars: []string{"out/soong/.intermediates/app/android_common/gen/android/R.srcjar"},
			appClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/shared/android_common/turbine-combined/shared.jar",
				"out/soong/.intermediates/direct/android_common/turbine-combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/turbine-combined/direct_import.jar",
			},
			appCombined: []string{
				"out/soong/.intermediates/app/android_common/javac/app.jar",
				"out/soong/.intermediates/direct/android_common/combined/direct.jar",
				"out/soong/.intermediates/direct_import/android_common/combined/direct_import.jar",
			},

			directResources: nil,
			directOverlays: []string{
				"out/soong/.intermediates/transitive/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import_dep/android_common/package-res.apk",
				"out/soong/.intermediates/transitive_import/android_common/package-res.apk",
				"out/soong/.intermediates/direct/android_common/aapt2/direct/res/values_strings.arsc.flat",
			},
			directImports: []string{"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk"},
			directSrcJars: []string{"out/soong/.intermediates/direct/android_common/gen/android/R.srcjar"},
			directClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/transitive/android_common/turbine-combined/transitive.jar",
				"out/soong/.intermediates/transitive_import/android_common/turbine-combined/transitive_import.jar",
			},
			directCombined: []string{
				"out/soong/.intermediates/direct/android_common/javac/direct.jar",
				"out/soong/.intermediates/transitive/android_common/javac/transitive.jar",
				"out/soong/.intermediates/transitive_import/android_common/combined/transitive_import.jar",
			},

			transitiveResources: []string{"out/soong/.intermediates/transitive/android_common/aapt2/transitive/res/values_strings.arsc.flat"},
			transitiveOverlays:  nil,
			transitiveImports:   []string{"out/soong/.intermediates/default/java/framework-res/android_common/package-res.apk"},
			transitiveSrcJars:   nil,
			transitiveClasspath: []string{
				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
				"out/soong/.intermediates/transitive/android_common/busybox/R.jar",
			},
			transitiveCombined: nil,

			dontVerifyShared:           true,
			dontVerifyDirectImport:     true,
			dontVerifyTransitiveImport: true,
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			bp := fmt.Sprintf(`
				android_app {
					name: "app",
					sdk_version: "current",
					srcs: ["app/app.java"],
					resource_dirs: ["app/res"],
					manifest: "app/AndroidManifest.xml",
					libs: ["shared"],
					static_libs: ["direct", "direct_import"],
					use_resource_processor: %v,
				}

				android_library {
					name: "direct",
					sdk_version: "current",
					srcs: ["direct/direct.java"],
					resource_dirs: ["direct/res"],
					manifest: "direct/AndroidManifest.xml",
					static_libs: ["transitive", "transitive_import"],
					use_resource_processor: %v,
				}

				android_library {
					name: "transitive",
					sdk_version: "current",
					srcs: ["transitive/transitive.java"],
					resource_dirs: ["transitive/res"],
					manifest: "transitive/AndroidManifest.xml",
					use_resource_processor: %v,
				}

				android_library {
					name: "shared",
					sdk_version: "current",
					srcs: ["shared/shared.java"],
					resource_dirs: ["shared/res"],
					manifest: "shared/AndroidManifest.xml",
					use_resource_processor: %v,
					libs: ["shared_transitive_shared"],
					static_libs: ["shared_transitive_static"],
				}

				android_library {
					name: "shared_transitive_shared",
					sdk_version: "current",
					srcs: ["shared_transitive_shared/shared_transitive_shared.java"],
					resource_dirs: ["shared_transitive_shared/res"],
					manifest: "shared_transitive_shared/AndroidManifest.xml",
					use_resource_processor: %v,
				}

				android_library {
					name: "shared_transitive_static",
					sdk_version: "current",
					srcs: ["shared_transitive_static/shared.java"],
					resource_dirs: ["shared_transitive_static/res"],
					manifest: "shared_transitive_static/AndroidManifest.xml",
					use_resource_processor: %v,
				}

				android_library_import {
					name: "direct_import",
					sdk_version: "current",
					aars: ["direct_import.aar"],
					static_libs: ["direct_import_dep"],
				}

				android_library_import {
					name: "direct_import_dep",
					sdk_version: "current",
					aars: ["direct_import_dep.aar"],
				}

				android_library_import {
					name: "transitive_import",
					sdk_version: "current",
					aars: ["transitive_import.aar"],
					static_libs: ["transitive_import_dep"],
				}

				android_library_import {
					name: "transitive_import_dep",
					sdk_version: "current",
					aars: ["transitive_import_dep.aar"],
				}
			`, testCase.appUsesRP, testCase.directLibUsesRP, testCase.transitiveLibUsesRP,
				testCase.sharedLibUsesRP, testCase.sharedTransitiveSharedLibUsesRP, testCase.sharedTransitiveStaticLibUsesRP)

			fs := android.MockFS{
				"app/res/values/strings.xml":                      nil,
				"direct/res/values/strings.xml":                   nil,
				"transitive/res/values/strings.xml":               nil,
				"shared/res/values/strings.xml":                   nil,
				"shared_transitive_static/res/values/strings.xml": nil,
				"shared_transitive_shared/res/values/strings.xml": nil,
			}

			result := android.GroupFixturePreparers(
				PrepareForTestWithJavaDefaultModules,
				fs.AddToFixture(),
			).RunTestWithBp(t, bp)

			type aaptInfo struct {
				resources, overlays, imports, srcJars, classpath, combined android.Paths
			}

			getAaptInfo := func(moduleName string) (aaptInfo aaptInfo) {
				mod := result.ModuleForTests(moduleName, "android_common")
				resourceListRule := mod.MaybeOutput("aapt2/res.list")
				overlayListRule := mod.MaybeOutput("aapt2/overlay.list")
				aaptRule := mod.Rule("aapt2Link")
				javacRule := mod.MaybeRule("javac")
				combinedRule := mod.MaybeOutput("combined/" + moduleName + ".jar")

				aaptInfo.resources = resourceListRule.Inputs
				aaptInfo.overlays = overlayListRule.Inputs

				aaptFlags := strings.Split(aaptRule.Args["flags"], " ")
				for i, flag := range aaptFlags {
					if flag == "-I" && i+1 < len(aaptFlags) {
						aaptInfo.imports = append(aaptInfo.imports, android.PathForTesting(aaptFlags[i+1]))
					}
				}

				if len(javacRule.Args["srcJars"]) > 0 {
					aaptInfo.srcJars = android.PathsForTesting(strings.Split(javacRule.Args["srcJars"], " ")...)
				}

				if len(javacRule.Args["classpath"]) > 0 {
					classpathArg := strings.TrimPrefix(javacRule.Args["classpath"], "-classpath ")
					aaptInfo.classpath = android.PathsForTesting(strings.Split(classpathArg, ":")...)
				}

				aaptInfo.combined = combinedRule.Inputs
				return
			}

			app := getAaptInfo("app")
			direct := getAaptInfo("direct")
			transitive := getAaptInfo("transitive")
			shared := getAaptInfo("shared")
			directImport := getAaptInfo("direct_import")
			transitiveImport := getAaptInfo("transitive_import")

			if !testCase.dontVerifyApp {
				android.AssertPathsRelativeToTopEquals(t, "app resources", testCase.appResources, app.resources)
				android.AssertPathsRelativeToTopEquals(t, "app overlays", testCase.appOverlays, app.overlays)
				android.AssertPathsRelativeToTopEquals(t, "app imports", testCase.appImports, app.imports)
				android.AssertPathsRelativeToTopEquals(t, "app srcjars", testCase.appSrcJars, app.srcJars)
				android.AssertPathsRelativeToTopEquals(t, "app classpath", testCase.appClasspath, app.classpath)
				android.AssertPathsRelativeToTopEquals(t, "app combined", testCase.appCombined, app.combined)
			}

			if !testCase.dontVerifyDirect {
				android.AssertPathsRelativeToTopEquals(t, "direct resources", testCase.directResources, direct.resources)
				android.AssertPathsRelativeToTopEquals(t, "direct overlays", testCase.directOverlays, direct.overlays)
				android.AssertPathsRelativeToTopEquals(t, "direct imports", testCase.directImports, direct.imports)
				android.AssertPathsRelativeToTopEquals(t, "direct srcjars", testCase.directSrcJars, direct.srcJars)
				android.AssertPathsRelativeToTopEquals(t, "direct classpath", testCase.directClasspath, direct.classpath)
				android.AssertPathsRelativeToTopEquals(t, "direct combined", testCase.directCombined, direct.combined)
			}

			if !testCase.dontVerifyTransitive {
				android.AssertPathsRelativeToTopEquals(t, "transitive resources", testCase.transitiveResources, transitive.resources)
				android.AssertPathsRelativeToTopEquals(t, "transitive overlays", testCase.transitiveOverlays, transitive.overlays)
				android.AssertPathsRelativeToTopEquals(t, "transitive imports", testCase.transitiveImports, transitive.imports)
				android.AssertPathsRelativeToTopEquals(t, "transitive srcjars", testCase.transitiveSrcJars, transitive.srcJars)
				android.AssertPathsRelativeToTopEquals(t, "transitive classpath", testCase.transitiveClasspath, transitive.classpath)
				android.AssertPathsRelativeToTopEquals(t, "transitive combined", testCase.transitiveCombined, transitive.combined)
			}

			if !testCase.dontVerifyShared {
				android.AssertPathsRelativeToTopEquals(t, "shared resources", testCase.sharedResources, shared.resources)
				android.AssertPathsRelativeToTopEquals(t, "shared overlays", testCase.sharedOverlays, shared.overlays)
				android.AssertPathsRelativeToTopEquals(t, "shared imports", testCase.sharedImports, shared.imports)
				android.AssertPathsRelativeToTopEquals(t, "shared srcjars", testCase.sharedSrcJars, shared.srcJars)
				android.AssertPathsRelativeToTopEquals(t, "shared classpath", testCase.sharedClasspath, shared.classpath)
				android.AssertPathsRelativeToTopEquals(t, "shared combined", testCase.sharedCombined, shared.combined)
			}

			if !testCase.dontVerifyDirectImport {
				android.AssertPathsRelativeToTopEquals(t, "direct_import resources", testCase.directImportResources, directImport.resources)
				android.AssertPathsRelativeToTopEquals(t, "direct_import overlays", testCase.directImportOverlays, directImport.overlays)
				android.AssertPathsRelativeToTopEquals(t, "direct_import imports", testCase.directImportImports, directImport.imports)
			}

			if !testCase.dontVerifyTransitiveImport {
				android.AssertPathsRelativeToTopEquals(t, "transitive_import resources", testCase.transitiveImportResources, transitiveImport.resources)
				android.AssertPathsRelativeToTopEquals(t, "transitive_import overlays", testCase.transitiveImportOverlays, transitiveImport.overlays)
				android.AssertPathsRelativeToTopEquals(t, "transitive_import imports", testCase.transitiveImportImports, transitiveImport.imports)
			}
		})
	}
}

func TestAndroidResourceOverlays(t *testing.T) {
	type moduleAndVariant struct {
		module  string
		variant string
	}

	testCases := []struct {
		name                       string
		enforceRROTargets          []string
		enforceRROExcludedOverlays []string
		resourceFiles              map[moduleAndVariant][]string
		overlayFiles               map[moduleAndVariant][]string
		rroDirs                    map[moduleAndVariant][]string
	}{
		{
			name:                       "no RRO",
			enforceRROTargets:          nil,
			enforceRROExcludedOverlays: nil,
			resourceFiles: map[moduleAndVariant][]string{
				{"foo", "android_common"}:  nil,
				{"bar", "android_common"}:  {"bar/res/res/values/strings.xml"},
				{"lib", "android_common"}:  nil,
				{"lib2", "android_common"}: {"lib2/res/res/values/strings.xml"},
			},
			overlayFiles: map[moduleAndVariant][]string{
				{"foo", "android_common"}: {
					"out/soong/.intermediates/lib2/android_common/package-res.apk",
					"out/soong/.intermediates/lib/android_common/package-res.apk",
					"out/soong/.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", "android_common"}: {
					"device/vendor/blah/static_overlay/bar/res/values/strings.xml",
					"device/vendor/blah/overlay/bar/res/values/strings.xml",
				},
				{"lib", "android_common"}: {
					"out/soong/.intermediates/lib2/android_common/package-res.apk",
					"lib/res/res/values/strings.xml",
					"device/vendor/blah/overlay/lib/res/values/strings.xml",
				},
			},
			rroDirs: map[moduleAndVariant][]string{
				{"foo", "android_common"}: nil,
				{"bar", "android_common"}: nil,
			},
		},
		{
			name:                       "enforce RRO on foo",
			enforceRROTargets:          []string{"foo"},
			enforceRROExcludedOverlays: []string{"device/vendor/blah/static_overlay"},
			resourceFiles: map[moduleAndVariant][]string{
				{"foo", "android_common"}:      nil,
				{"bar", "android_common"}:      {"bar/res/res/values/strings.xml"},
				{"lib", "android_common"}:      nil,
				{"lib", "android_common_rro"}:  nil,
				{"lib2", "android_common"}:     {"lib2/res/res/values/strings.xml"},
				{"lib2", "android_common_rro"}: {"lib2/res/res/values/strings.xml"},
			},
			overlayFiles: map[moduleAndVariant][]string{
				{"foo", "android_common"}: {
					"out/soong/.intermediates/lib2/android_common_rro/package-res.apk",
					"out/soong/.intermediates/lib/android_common_rro/package-res.apk",
					"out/soong/.intermediates/lib3/android_common_rro/package-res.apk",
					"foo/res/res/values/strings.xml",
					"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
				},
				{"bar", "android_common"}: {
					"device/vendor/blah/static_overlay/bar/res/values/strings.xml",
					"device/vendor/blah/overlay/bar/res/values/strings.xml",
				},
				{"lib", "android_common"}: {
					"out/soong/.intermediates/lib2/android_common/package-res.apk",
					"lib/res/res/values/strings.xml",
					"device/vendor/blah/overlay/lib/res/values/strings.xml",
				},
				{"lib", "android_common_rro"}: {
					"out/soong/.intermediates/lib2/android_common_rro/package-res.apk",
					"lib/res/res/values/strings.xml",
				},
			},

			rroDirs: map[moduleAndVariant][]string{
				{"foo", "android_common"}: {
					"device:device/vendor/blah/overlay/foo/res",
					"product:product/vendor/blah/overlay/foo/res",
					"device:device/vendor/blah/overlay/lib/res",
				},
				{"bar", "android_common"}:     nil,
				{"lib", "android_common"}:     nil,
				{"lib", "android_common_rro"}: {"device:device/vendor/blah/overlay/lib/res"},
			},
		},
		{
			name:              "enforce RRO on all",
			enforceRROTargets: []string{"*"},
			enforceRROExcludedOverlays: []string{
				// Excluding specific apps/res directories also allowed.
				"device/vendor/blah/static_overlay/foo",
				"device/vendor/blah/static_overlay/bar/res",
			},
			resourceFiles: map[moduleAndVariant][]string{
				{"foo", "android_common"}:  nil,
				{"bar", "android_common"}:  {"bar/res/res/values/strings.xml"},
				{"lib", "android_common"}:  nil,
				{"lib2", "android_common"}: {"lib2/res/res/values/strings.xml"},
			},
			overlayFiles: map[moduleAndVariant][]string{
				{"foo", "android_common"}: {
					"out/soong/.intermediates/lib2/android_common/package-res.apk",
					"out/soong/.intermediates/lib/android_common/package-res.apk",
					"out/soong/.intermediates/lib3/android_common/package-res.apk",
					"foo/res/res/values/strings.xml",
					"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
				},
				{"bar", "android_common"}: {"device/vendor/blah/static_overlay/bar/res/values/strings.xml"},
				{"lib", "android_common"}: {
					"out/soong/.intermediates/lib2/android_common/package-res.apk",
					"lib/res/res/values/strings.xml",
				},
			},
			rroDirs: map[moduleAndVariant][]string{
				{"foo", "android_common"}: {
					"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", "android_common"}: {"device:device/vendor/blah/overlay/bar/res"},
				{"lib", "android_common"}: {"device:device/vendor/blah/overlay/lib/res"},
			},
		},
	}

	deviceResourceOverlays := []string{
		"device/vendor/blah/overlay",
		"device/vendor/blah/overlay2",
		"device/vendor/blah/static_overlay",
	}

	productResourceOverlays := []string{
		"product/vendor/blah/overlay",
	}

	fs := android.MockFS{
		"foo/res/res/values/strings.xml":                               nil,
		"bar/res/res/values/strings.xml":                               nil,
		"lib/res/res/values/strings.xml":                               nil,
		"lib2/res/res/values/strings.xml":                              nil,
		"device/vendor/blah/overlay/foo/res/values/strings.xml":        nil,
		"device/vendor/blah/overlay/bar/res/values/strings.xml":        nil,
		"device/vendor/blah/overlay/lib/res/values/strings.xml":        nil,
		"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",
				sdk_version: "current",
				resource_dirs: ["foo/res"],
				static_libs: ["lib", "lib3"],
			}

			android_app {
				name: "bar",
				sdk_version: "current",
				resource_dirs: ["bar/res"],
			}

			android_library {
				name: "lib",
				sdk_version: "current",
				resource_dirs: ["lib/res"],
				static_libs: ["lib2"],
			}

			android_library {
				name: "lib2",
				sdk_version: "current",
				resource_dirs: ["lib2/res"],
			}

			// This library has the same resources as lib (should not lead to dupe RROs)
			android_library {
				name: "lib3",
				sdk_version: "current",
				resource_dirs: ["lib/res"]
			}
		`

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			result := android.GroupFixturePreparers(
				PrepareForTestWithJavaDefaultModules,
				fs.AddToFixture(),
				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
					variables.DeviceResourceOverlays = deviceResourceOverlays
					variables.ProductResourceOverlays = productResourceOverlays
					if testCase.enforceRROTargets != nil {
						variables.EnforceRROTargets = testCase.enforceRROTargets
					}
					if testCase.enforceRROExcludedOverlays != nil {
						variables.EnforceRROExcludedOverlays = testCase.enforceRROExcludedOverlays
					}
				}),
			).RunTestWithBp(t, bp)

			resourceListToFiles := func(module android.TestingModule, list []string) (files []string) {
				for _, o := range list {
					res := module.MaybeOutput(o)
					if res.Rule != nil {
						// If the overlay is compiled as part of this moduleAndVariant (i.e. a .arsc.flat file),
						// verify the inputs to the .arsc.flat rule.
						files = append(files, res.Inputs.Strings()...)
					} else {
						// Otherwise, verify the full path to the output of the other moduleAndVariant
						files = append(files, o)
					}
				}
				return files
			}

			getResources := func(moduleName, variantName string) (resourceFiles, overlayFiles, rroDirs []string) {
				module := result.ModuleForTests(moduleName, variantName)
				resourceList := module.MaybeOutput("aapt2/res.list")
				if resourceList.Rule != nil {
					resourceFiles = resourceListToFiles(module, android.PathsRelativeToTop(resourceList.Inputs))
				}
				overlayList := module.MaybeOutput("aapt2/overlay.list")
				if overlayList.Rule != nil {
					overlayFiles = resourceListToFiles(module, android.PathsRelativeToTop(overlayList.Inputs))
				}

				for _, d := range module.Module().(AndroidLibraryDependency).RRODirsDepSet().ToList() {
					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+android.PathRelativeToTop(d.path))
				}

				return resourceFiles, overlayFiles, rroDirs
			}

			modules := []moduleAndVariant{
				{"foo", "android_common"},
				{"foo", "android_common_rro"},
				{"bar", "android_common"},
				{"bar", "android_common_rro"},
				{"lib", "android_common"},
				{"lib", "android_common_rro"},
				{"lib2", "android_common"},
				{"lib2", "android_common_rro"},
			}
			for _, moduleAndVariant := range modules {
				if _, exists := testCase.resourceFiles[moduleAndVariant]; !exists {
					continue
				}
				resourceFiles, overlayFiles, rroDirs := getResources(moduleAndVariant.module, moduleAndVariant.variant)

				if !reflect.DeepEqual(resourceFiles, testCase.resourceFiles[moduleAndVariant]) {
					t.Errorf("expected %s resource files:\n  %#v\n got:\n  %#v",
						moduleAndVariant, testCase.resourceFiles[moduleAndVariant], resourceFiles)
				}
				if !reflect.DeepEqual(overlayFiles, testCase.overlayFiles[moduleAndVariant]) {
					t.Errorf("expected %s overlay files:\n  %#v\n got:\n  %#v",
						moduleAndVariant, testCase.overlayFiles[moduleAndVariant], overlayFiles)
				}
				if !reflect.DeepEqual(rroDirs, testCase.rroDirs[moduleAndVariant]) {
					t.Errorf("expected %s rroDirs:  %#v\n got:\n  %#v",
						moduleAndVariant, testCase.rroDirs[moduleAndVariant], rroDirs)
				}
			}
		})
	}
}

func checkSdkVersion(t *testing.T, result *android.TestResult, expectedSdkVersion string) {
	foo := result.ModuleForTests("foo", "android_common")
	link := foo.Output("package-res.apk")
	linkFlags := strings.Split(link.Args["flags"], " ")
	min := android.IndexList("--min-sdk-version", linkFlags)
	target := android.IndexList("--target-sdk-version", linkFlags)

	if min == -1 || target == -1 || min == len(linkFlags)-1 || target == len(linkFlags)-1 {
		t.Fatalf("missing --min-sdk-version or --target-sdk-version in link flags: %q", linkFlags)
	}

	gotMinSdkVersion := linkFlags[min+1]
	gotTargetSdkVersion := linkFlags[target+1]

	android.AssertStringEquals(t, "incorrect --min-sdk-version", expectedSdkVersion, gotMinSdkVersion)

	android.AssertStringEquals(t, "incorrect --target-sdk-version", expectedSdkVersion, gotTargetSdkVersion)
}

func TestAppSdkVersion(t *testing.T) {
	testCases := []struct {
		name                  string
		sdkVersion            string
		platformSdkInt        int
		platformSdkCodename   string
		platformSdkFinal      bool
		minSdkVersionBp       string
		expectedMinSdkVersion string
		platformApis          bool
		activeCodenames       []string
	}{
		{
			name:                  "current final SDK",
			sdkVersion:            "current",
			platformSdkInt:        27,
			platformSdkCodename:   "REL",
			platformSdkFinal:      true,
			expectedMinSdkVersion: "27",
		},
		{
			name:                  "current non-final SDK",
			sdkVersion:            "current",
			platformSdkInt:        27,
			platformSdkCodename:   "OMR1",
			platformSdkFinal:      false,
			expectedMinSdkVersion: "OMR1",
			activeCodenames:       []string{"OMR1"},
		},
		{
			name:                  "default final SDK",
			sdkVersion:            "",
			platformApis:          true,
			platformSdkInt:        27,
			platformSdkCodename:   "REL",
			platformSdkFinal:      true,
			expectedMinSdkVersion: "27",
		},
		{
			name:                  "default non-final SDK",
			sdkVersion:            "",
			platformApis:          true,
			platformSdkInt:        27,
			platformSdkCodename:   "OMR1",
			platformSdkFinal:      false,
			expectedMinSdkVersion: "OMR1",
			activeCodenames:       []string{"OMR1"},
		},
		{
			name:                  "14",
			sdkVersion:            "14",
			expectedMinSdkVersion: "14",
			platformSdkCodename:   "S",
			activeCodenames:       []string{"S"},
		},
		{
			name:                  "two active SDKs",
			sdkVersion:            "module_current",
			minSdkVersionBp:       "UpsideDownCake",
			expectedMinSdkVersion: "UpsideDownCake", // And not VanillaIceCream
			platformSdkCodename:   "VanillaIceCream",
			activeCodenames:       []string{"UpsideDownCake", "VanillaIceCream"},
		},
	}

	for _, moduleType := range []string{"android_app", "android_library"} {
		for _, test := range testCases {
			t.Run(moduleType+" "+test.name, func(t *testing.T) {
				platformApiProp := ""
				if test.platformApis {
					platformApiProp = "platform_apis: true,"
				}
				minSdkVersionProp := ""
				if test.minSdkVersionBp != "" {
					minSdkVersionProp = fmt.Sprintf(` min_sdk_version: "%s",`, test.minSdkVersionBp)
				}
				bp := fmt.Sprintf(`%s {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "%s",
					%s
					%s
				}`, moduleType, test.sdkVersion, platformApiProp, minSdkVersionProp)

				result := android.GroupFixturePreparers(
					prepareForJavaTest,
					android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
						variables.Platform_sdk_version = &test.platformSdkInt
						variables.Platform_sdk_codename = &test.platformSdkCodename
						variables.Platform_version_active_codenames = test.activeCodenames
						variables.Platform_sdk_final = &test.platformSdkFinal
					}),
					FixtureWithPrebuiltApis(map[string][]string{
						"14": {"foo"},
					}),
				).RunTestWithBp(t, bp)

				checkSdkVersion(t, result, test.expectedMinSdkVersion)
			})
		}
	}
}

func TestVendorAppSdkVersion(t *testing.T) {
	testCases := []struct {
		name                                  string
		sdkVersion                            string
		platformSdkInt                        int
		platformSdkCodename                   string
		platformSdkFinal                      bool
		deviceCurrentApiLevelForVendorModules string
		expectedMinSdkVersion                 string
	}{
		{
			name:                                  "current final SDK",
			sdkVersion:                            "current",
			platformSdkInt:                        29,
			platformSdkCodename:                   "REL",
			platformSdkFinal:                      true,
			deviceCurrentApiLevelForVendorModules: "29",
			expectedMinSdkVersion:                 "29",
		},
		{
			name:                                  "current final SDK",
			sdkVersion:                            "current",
			platformSdkInt:                        29,
			platformSdkCodename:                   "REL",
			platformSdkFinal:                      true,
			deviceCurrentApiLevelForVendorModules: "28",
			expectedMinSdkVersion:                 "28",
		},
		{
			name:                                  "current final SDK",
			sdkVersion:                            "current",
			platformSdkInt:                        29,
			platformSdkCodename:                   "Q",
			platformSdkFinal:                      false,
			deviceCurrentApiLevelForVendorModules: "28",
			expectedMinSdkVersion:                 "28",
		},
	}

	for _, moduleType := range []string{"android_app", "android_library"} {
		for _, sdkKind := range []string{"", "system_"} {
			for _, test := range testCases {
				t.Run(moduleType+" "+test.name, func(t *testing.T) {
					bp := fmt.Sprintf(`%s {
						name: "foo",
						srcs: ["a.java"],
						sdk_version: "%s%s",
						vendor: true,
					}`, moduleType, sdkKind, test.sdkVersion)

					result := android.GroupFixturePreparers(
						prepareForJavaTest,
						android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
							variables.Platform_sdk_version = &test.platformSdkInt
							variables.Platform_sdk_codename = &test.platformSdkCodename
							variables.Platform_sdk_final = &test.platformSdkFinal
							variables.DeviceCurrentApiLevelForVendorModules = &test.deviceCurrentApiLevelForVendorModules
							variables.DeviceSystemSdkVersions = []string{"28", "29"}
						}),
						FixtureWithPrebuiltApis(map[string][]string{
							"28":      {"foo"},
							"29":      {"foo"},
							"current": {"foo"},
						}),
					).RunTestWithBp(t, bp)

					checkSdkVersion(t, result, test.expectedMinSdkVersion)
				})
			}
		}
	}
}

func TestJNIABI(t *testing.T) {
	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
		cc_library {
			name: "libjni",
			system_shared_libs: [],
			sdk_version: "current",
			stl: "none",
		}

		android_test {
			name: "test",
			sdk_version: "core_platform",
			jni_libs: ["libjni"],
		}

		android_test {
			name: "test_first",
			sdk_version: "core_platform",
			compile_multilib: "first",
			jni_libs: ["libjni"],
		}

		android_test {
			name: "test_both",
			sdk_version: "core_platform",
			compile_multilib: "both",
			jni_libs: ["libjni"],
		}

		android_test {
			name: "test_32",
			sdk_version: "core_platform",
			compile_multilib: "32",
			jni_libs: ["libjni"],
		}

		android_test {
			name: "test_64",
			sdk_version: "core_platform",
			compile_multilib: "64",
			jni_libs: ["libjni"],
		}
		`)

	testCases := []struct {
		name string
		abis []string
	}{
		{"test", []string{"arm64-v8a"}},
		{"test_first", []string{"arm64-v8a"}},
		{"test_both", []string{"arm64-v8a", "armeabi-v7a"}},
		{"test_32", []string{"armeabi-v7a"}},
		{"test_64", []string{"arm64-v8a"}},
	}

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			app := ctx.ModuleForTests(test.name, "android_common")
			jniLibZip := app.Output("jnilibs.zip")
			var abis []string
			args := strings.Fields(jniLibZip.Args["jarArgs"])
			for i := 0; i < len(args); i++ {
				if args[i] == "-P" {
					abis = append(abis, filepath.Base(args[i+1]))
					i++
				}
			}
			if !reflect.DeepEqual(abis, test.abis) {
				t.Errorf("want abis %v, got %v", test.abis, abis)
			}
		})
	}
}

func TestAppSdkVersionByPartition(t *testing.T) {
	testJavaError(t, "sdk_version must have a value when the module is located at vendor or product", `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			vendor: true,
			platform_apis: true,
		}
	`)

	testJava(t, `
		android_app {
			name: "bar",
			srcs: ["b.java"],
			platform_apis: true,
		}
	`)

	for _, enforce := range []bool{true, false} {
		bp := `
			android_app {
				name: "foo",
				srcs: ["a.java"],
				product_specific: true,
				platform_apis: true,
			}
		`

		errorHandler := android.FixtureExpectsNoErrors
		if enforce {
			errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern("sdk_version must have a value when the module is located at vendor or product")
		}

		android.GroupFixturePreparers(
			PrepareForTestWithJavaDefaultModules,
			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				variables.EnforceProductPartitionInterface = proptools.BoolPtr(enforce)
			}),
		).
			ExtendWithErrorHandler(errorHandler).
			RunTestWithBp(t, bp)
	}
}

func TestJNIPackaging(t *testing.T) {
	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
		cc_library {
			name: "libjni",
			system_shared_libs: [],
			stl: "none",
			sdk_version: "current",
		}

		android_app {
			name: "app",
			jni_libs: ["libjni"],
			sdk_version: "current",
		}

		android_app {
			name: "app_noembed",
			jni_libs: ["libjni"],
			use_embedded_native_libs: false,
			sdk_version: "current",
		}

		android_app {
			name: "app_embed",
			jni_libs: ["libjni"],
			use_embedded_native_libs: true,
			sdk_version: "current",
		}

		android_test {
			name: "test",
			sdk_version: "current",
			jni_libs: ["libjni"],
		}

		android_test {
			name: "test_noembed",
			sdk_version: "current",
			jni_libs: ["libjni"],
			use_embedded_native_libs: false,
		}

		android_test_helper_app {
			name: "test_helper",
			sdk_version: "current",
			jni_libs: ["libjni"],
		}

		android_test_helper_app {
			name: "test_helper_noembed",
			sdk_version: "current",
			jni_libs: ["libjni"],
			use_embedded_native_libs: false,
		}
		`)

	testCases := []struct {
		name       string
		packaged   bool
		compressed bool
	}{
		{"app", false, false},
		{"app_noembed", false, false},
		{"app_embed", true, false},
		{"test", true, false},
		{"test_noembed", true, true},
		{"test_helper", true, false},
		{"test_helper_noembed", true, true},
	}

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			app := ctx.ModuleForTests(test.name, "android_common")
			jniLibZip := app.MaybeOutput("jnilibs.zip")
			if g, w := (jniLibZip.Rule != nil), test.packaged; g != w {
				t.Errorf("expected jni packaged %v, got %v", w, g)
			}

			if jniLibZip.Rule != nil {
				if g, w := !strings.Contains(jniLibZip.Args["jarArgs"], "-L 0"), test.compressed; g != w {
					t.Errorf("expected jni compressed %v, got %v", w, g)
				}

				if !strings.Contains(jniLibZip.Implicits[0].String(), "_sdk_") {
					t.Errorf("expected input %q to use sdk variant", jniLibZip.Implicits[0].String())
				}
			}
		})
	}
}

func TestJNISDK(t *testing.T) {
	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
		cc_library {
			name: "libjni",
			system_shared_libs: [],
			stl: "none",
			sdk_version: "current",
		}

		android_test {
			name: "app_platform",
			jni_libs: ["libjni"],
			platform_apis: true,
		}

		android_test {
			name: "app_sdk",
			jni_libs: ["libjni"],
			sdk_version: "current",
		}

		android_test {
			name: "app_force_platform",
			jni_libs: ["libjni"],
			sdk_version: "current",
			jni_uses_platform_apis: true,
		}

		android_test {
			name: "app_force_sdk",
			jni_libs: ["libjni"],
			platform_apis: true,
			jni_uses_sdk_apis: true,
		}

		cc_library {
			name: "libvendorjni",
			system_shared_libs: [],
			stl: "none",
			vendor: true,
		}

		android_test {
			name: "app_vendor",
			jni_libs: ["libvendorjni"],
			sdk_version: "current",
			vendor: true,
		}
	`)

	testCases := []struct {
		name      string
		sdkJNI    bool
		vendorJNI bool
	}{
		{name: "app_platform"},
		{name: "app_sdk", sdkJNI: true},
		{name: "app_force_platform"},
		{name: "app_force_sdk", sdkJNI: true},
		{name: "app_vendor", vendorJNI: true},
	}

	platformJNI := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_shared").
		Output("libjni.so").Output.String()
	sdkJNI := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_sdk_shared").
		Output("libjni.so").Output.String()
	vendorJNI := ctx.ModuleForTests("libvendorjni", "android_vendor_arm64_armv8-a_shared").
		Output("libvendorjni.so").Output.String()

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			app := ctx.ModuleForTests(test.name, "android_common")

			jniLibZip := app.MaybeOutput("jnilibs.zip")
			if len(jniLibZip.Implicits) != 1 {
				t.Fatalf("expected exactly one jni library, got %q", jniLibZip.Implicits.Strings())
			}
			gotJNI := jniLibZip.Implicits[0].String()

			if test.sdkJNI {
				if gotJNI != sdkJNI {
					t.Errorf("expected SDK JNI library %q, got %q", sdkJNI, gotJNI)
				}
			} else if test.vendorJNI {
				if gotJNI != vendorJNI {
					t.Errorf("expected platform JNI library %q, got %q", vendorJNI, gotJNI)
				}
			} else {
				if gotJNI != platformJNI {
					t.Errorf("expected platform JNI library %q, got %q", platformJNI, gotJNI)
				}
			}
		})
	}

	t.Run("jni_uses_platform_apis_error", func(t *testing.T) {
		testJavaError(t, `jni_uses_platform_apis: can only be set for modules that set sdk_version`, `
			android_test {
				name: "app_platform",
				platform_apis: true,
				jni_uses_platform_apis: true,
			}
		`)
	})

	t.Run("jni_uses_sdk_apis_error", func(t *testing.T) {
		testJavaError(t, `jni_uses_sdk_apis: can only be set for modules that do not set sdk_version`, `
			android_test {
				name: "app_sdk",
				sdk_version: "current",
				jni_uses_sdk_apis: true,
			}
		`)
	})

}

func TestCertificates(t *testing.T) {
	testCases := []struct {
		name                     string
		bp                       string
		allowMissingDependencies bool
		certificateOverride      string
		expectedCertSigningFlags string
		expectedCertificate      string
	}{
		{
			name: "default",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
				}
			`,
			certificateOverride:      "",
			expectedCertSigningFlags: "",
			expectedCertificate:      "build/make/target/product/security/testkey",
		},
		{
			name: "module certificate property",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					certificate: ":new_certificate",
					sdk_version: "current",
				}

				android_app_certificate {
					name: "new_certificate",
					certificate: "cert/new_cert",
				}
			`,
			certificateOverride:      "",
			expectedCertSigningFlags: "",
			expectedCertificate:      "cert/new_cert",
		},
		{
			name: "path certificate property",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					certificate: "expiredkey",
					sdk_version: "current",
				}
			`,
			certificateOverride:      "",
			expectedCertSigningFlags: "",
			expectedCertificate:      "build/make/target/product/security/expiredkey",
		},
		{
			name: "certificate overrides",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					certificate: "expiredkey",
					sdk_version: "current",
				}

				android_app_certificate {
					name: "new_certificate",
					certificate: "cert/new_cert",
				}
			`,
			certificateOverride:      "foo:new_certificate",
			expectedCertSigningFlags: "",
			expectedCertificate:      "cert/new_cert",
		},
		{
			name: "certificate signing flags",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					certificate: ":new_certificate",
					lineage: "lineage.bin",
					rotationMinSdkVersion: "32",
					sdk_version: "current",
				}

				android_app_certificate {
					name: "new_certificate",
					certificate: "cert/new_cert",
				}
			`,
			certificateOverride:      "",
			expectedCertSigningFlags: "--lineage lineage.bin --rotation-min-sdk-version 32",
			expectedCertificate:      "cert/new_cert",
		},
		{
			name: "cert signing flags from filegroup",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					certificate: ":new_certificate",
					lineage: ":lineage_bin",
					rotationMinSdkVersion: "32",
					sdk_version: "current",
				}

				android_app_certificate {
					name: "new_certificate",
					certificate: "cert/new_cert",
				}

				filegroup {
					name: "lineage_bin",
					srcs: ["lineage.bin"],
				}
			`,
			certificateOverride:      "",
			expectedCertSigningFlags: "--lineage lineage.bin --rotation-min-sdk-version 32",
			expectedCertificate:      "cert/new_cert",
		},
		{
			name: "missing with AllowMissingDependencies",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					certificate: ":new_certificate",
					sdk_version: "current",
				}
			`,
			expectedCertificate:      "out/soong/.intermediates/foo/android_common/missing",
			allowMissingDependencies: true,
		},
	}

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			result := android.GroupFixturePreparers(
				PrepareForTestWithJavaDefaultModules,
				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
					if test.certificateOverride != "" {
						variables.CertificateOverrides = []string{test.certificateOverride}
					}
					if test.allowMissingDependencies {
						variables.Allow_missing_dependencies = proptools.BoolPtr(true)
					}
				}),
				android.FixtureModifyContext(func(ctx *android.TestContext) {
					ctx.SetAllowMissingDependencies(test.allowMissingDependencies)
				}),
			).RunTestWithBp(t, test.bp)

			foo := result.ModuleForTests("foo", "android_common")

			certificate := foo.Module().(*AndroidApp).certificate
			android.AssertPathRelativeToTopEquals(t, "certificates key", test.expectedCertificate+".pk8", certificate.Key)
			// The sign_target_files_apks and check_target_files_signatures
			// tools require that certificates have a .x509.pem extension.
			android.AssertPathRelativeToTopEquals(t, "certificates pem", test.expectedCertificate+".x509.pem", certificate.Pem)

			signapk := foo.Output("foo.apk")
			if signapk.Rule != android.ErrorRule {
				signCertificateFlags := signapk.Args["certificates"]
				expectedFlags := certificate.Pem.String() + " " + certificate.Key.String()
				android.AssertStringEquals(t, "certificates flags", expectedFlags, signCertificateFlags)

				certSigningFlags := signapk.Args["flags"]
				android.AssertStringEquals(t, "cert signing flags", test.expectedCertSigningFlags, certSigningFlags)
			}
		})
	}
}

func TestRequestV4SigningFlag(t *testing.T) {
	testCases := []struct {
		name     string
		bp       string
		expected string
	}{
		{
			name: "default",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
				}
			`,
			expected: "",
		},
		{
			name: "default",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
					v4_signature: false,
				}
			`,
			expected: "",
		},
		{
			name: "module certificate property",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
					v4_signature: true,
				}
			`,
			expected: "--enable-v4",
		},
	}

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			result := android.GroupFixturePreparers(
				PrepareForTestWithJavaDefaultModules,
			).RunTestWithBp(t, test.bp)

			foo := result.ModuleForTests("foo", "android_common")

			signapk := foo.Output("foo.apk")
			signFlags := signapk.Args["flags"]
			android.AssertStringEquals(t, "signing flags", test.expected, signFlags)
		})
	}
}

func TestPackageNameOverride(t *testing.T) {
	testCases := []struct {
		name                string
		bp                  string
		packageNameOverride string
		expected            []string
	}{
		{
			name: "default",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
				}
			`,
			packageNameOverride: "",
			expected: []string{
				"out/soong/.intermediates/foo/android_common/foo.apk",
				"out/soong/target/product/test_device/system/app/foo/foo.apk",
			},
		},
		{
			name: "overridden via PRODUCT_PACKAGE_NAME_OVERRIDES",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
				}
			`,
			packageNameOverride: "foo:bar",
			expected: []string{
				// The package apk should be still be the original name for test dependencies.
				"out/soong/.intermediates/foo/android_common/bar.apk",
				"out/soong/target/product/test_device/system/app/bar/bar.apk",
			},
		},
		{
			name: "overridden via stem",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
					stem: "bar",
				}
			`,
			packageNameOverride: "",
			expected: []string{
				"out/soong/.intermediates/foo/android_common/bar.apk",
				"out/soong/target/product/test_device/system/app/bar/bar.apk",
			},
		},
	}

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			result := android.GroupFixturePreparers(
				PrepareForTestWithJavaDefaultModules,
				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
					if test.packageNameOverride != "" {
						variables.PackageNameOverrides = []string{test.packageNameOverride}
					}
				}),
			).RunTestWithBp(t, test.bp)

			foo := result.ModuleForTests("foo", "android_common")

			outSoongDir := result.Config.SoongOutDir()

			outputs := foo.AllOutputs()
			outputMap := make(map[string]bool)
			for _, o := range outputs {
				outputMap[android.StringPathRelativeToTop(outSoongDir, o)] = true
			}
			for _, e := range test.expected {
				if _, exist := outputMap[e]; !exist {
					t.Errorf("Can't find %q in output files.\nAll outputs:%v", e, outputs)
				}
			}
		})
	}
}

func TestInstrumentationTargetOverridden(t *testing.T) {
	bp := `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
		}

		android_test {
			name: "bar",
			instrumentation_for: "foo",
			sdk_version: "current",
		}
		`

	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
			variables.ManifestPackageNameOverrides = []string{"foo:org.dandroid.bp"}
		}),
	).RunTestWithBp(t, bp)

	bar := result.ModuleForTests("bar", "android_common")
	res := bar.Output("package-res.apk")
	aapt2Flags := res.Args["flags"]
	e := "--rename-instrumentation-target-package org.dandroid.bp"
	if !strings.Contains(aapt2Flags, e) {
		t.Errorf("target package renaming flag, %q is missing in aapt2 link flags, %q", e, aapt2Flags)
	}
}

func TestOverrideAndroidApp(t *testing.T) {
	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(
		t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			certificate: "expiredkey",
			overrides: ["qux"],
			sdk_version: "current",
		}

		override_android_app {
			name: "bar",
			base: "foo",
			certificate: ":new_certificate",
			lineage: "lineage.bin",
			rotationMinSdkVersion: "32",
			logging_parent: "bah",
		}

		android_app_certificate {
			name: "new_certificate",
			certificate: "cert/new_cert",
		}

		override_android_app {
			name: "baz",
			base: "foo",
			package_name: "org.dandroid.bp",
		}

		override_android_app {
			name: "baz_no_rename_resources",
			base: "foo",
			package_name: "org.dandroid.bp",
			rename_resources_package: false,
		}

		android_app {
			name: "foo_no_rename_resources",
			srcs: ["a.java"],
			certificate: "expiredkey",
			overrides: ["qux"],
			rename_resources_package: false,
			sdk_version: "current",
		}

		override_android_app {
			name: "baz_base_no_rename_resources",
			base: "foo_no_rename_resources",
			package_name: "org.dandroid.bp",
		}

		override_android_app {
			name: "baz_override_base_rename_resources",
			base: "foo_no_rename_resources",
			package_name: "org.dandroid.bp",
			rename_resources_package: true,
		}
		`)

	expectedVariants := []struct {
		name             string
		moduleName       string
		variantName      string
		apkName          string
		apkPath          string
		certFlag         string
		certSigningFlags string
		overrides        []string
		packageFlag      string
		renameResources  bool
		logging_parent   string
	}{
		{
			name:             "foo",
			moduleName:       "foo",
			variantName:      "android_common",
			apkPath:          "out/soong/target/product/test_device/system/app/foo/foo.apk",
			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
			certSigningFlags: "",
			overrides:        []string{"qux"},
			packageFlag:      "",
			renameResources:  false,
			logging_parent:   "",
		},
		{
			name:             "foo",
			moduleName:       "bar",
			variantName:      "android_common_bar",
			apkPath:          "out/soong/target/product/test_device/system/app/bar/bar.apk",
			certFlag:         "cert/new_cert.x509.pem cert/new_cert.pk8",
			certSigningFlags: "--lineage lineage.bin --rotation-min-sdk-version 32",
			overrides:        []string{"qux", "foo"},
			packageFlag:      "",
			renameResources:  false,
			logging_parent:   "bah",
		},
		{
			name:             "foo",
			moduleName:       "baz",
			variantName:      "android_common_baz",
			apkPath:          "out/soong/target/product/test_device/system/app/baz/baz.apk",
			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
			certSigningFlags: "",
			overrides:        []string{"qux", "foo"},
			packageFlag:      "org.dandroid.bp",
			renameResources:  true,
			logging_parent:   "",
		},
		{
			name:             "foo",
			moduleName:       "baz_no_rename_resources",
			variantName:      "android_common_baz_no_rename_resources",
			apkPath:          "out/soong/target/product/test_device/system/app/baz_no_rename_resources/baz_no_rename_resources.apk",
			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
			certSigningFlags: "",
			overrides:        []string{"qux", "foo"},
			packageFlag:      "org.dandroid.bp",
			renameResources:  false,
			logging_parent:   "",
		},
		{
			name:             "foo_no_rename_resources",
			moduleName:       "baz_base_no_rename_resources",
			variantName:      "android_common_baz_base_no_rename_resources",
			apkPath:          "out/soong/target/product/test_device/system/app/baz_base_no_rename_resources/baz_base_no_rename_resources.apk",
			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
			certSigningFlags: "",
			overrides:        []string{"qux", "foo_no_rename_resources"},
			packageFlag:      "org.dandroid.bp",
			renameResources:  false,
			logging_parent:   "",
		},
		{
			name:             "foo_no_rename_resources",
			moduleName:       "baz_override_base_rename_resources",
			variantName:      "android_common_baz_override_base_rename_resources",
			apkPath:          "out/soong/target/product/test_device/system/app/baz_override_base_rename_resources/baz_override_base_rename_resources.apk",
			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
			certSigningFlags: "",
			overrides:        []string{"qux", "foo_no_rename_resources"},
			packageFlag:      "org.dandroid.bp",
			renameResources:  true,
			logging_parent:   "",
		},
	}
	for _, expected := range expectedVariants {
		variant := result.ModuleForTests(expected.name, expected.variantName)

		// Check the final apk name
		variant.Output(expected.apkPath)

		// Check the certificate paths
		signapk := variant.Output(expected.moduleName + ".apk")
		certFlag := signapk.Args["certificates"]
		android.AssertStringEquals(t, "certificates flags", expected.certFlag, certFlag)

		// Check the cert signing flags
		certSigningFlags := signapk.Args["flags"]
		android.AssertStringEquals(t, "cert signing flags", expected.certSigningFlags, certSigningFlags)

		// Check if the overrides field values are correctly aggregated.
		mod := variant.Module().(*AndroidApp)
		android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.overridableAppProperties.Overrides)

		// Test Overridable property: Logging_parent
		logging_parent := mod.aapt.LoggingParent
		android.AssertStringEquals(t, "overrides property value for logging parent", expected.logging_parent, logging_parent)

		// Check the package renaming flag, if exists.
		res := variant.Output("package-res.apk")
		aapt2Flags := res.Args["flags"]
		checkAapt2LinkFlag(t, aapt2Flags, "rename-manifest-package", expected.packageFlag)
		expectedPackage := expected.packageFlag
		if !expected.renameResources {
			expectedPackage = ""
		}
		checkAapt2LinkFlag(t, aapt2Flags, "rename-resources-package", expectedPackage)
	}
}

func TestOverrideAndroidAppOverrides(t *testing.T) {
	ctx, _ := testJava(
		t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
			overrides: ["qux"]
		}

		android_app {
			name: "bar",
			srcs: ["b.java"],
			sdk_version: "current",
			overrides: ["foo"]
		}

		override_android_app {
			name: "foo_override",
			base: "foo",
			overrides: ["bar"]
		}
		`)

	expectedVariants := []struct {
		name        string
		moduleName  string
		variantName string
		overrides   []string
	}{
		{
			name:        "foo",
			moduleName:  "foo",
			variantName: "android_common",
			overrides:   []string{"qux"},
		},
		{
			name:        "bar",
			moduleName:  "bar",
			variantName: "android_common",
			overrides:   []string{"foo"},
		},
		{
			name:        "foo",
			moduleName:  "foo_override",
			variantName: "android_common_foo_override",
			overrides:   []string{"bar", "foo"},
		},
	}
	for _, expected := range expectedVariants {
		variant := ctx.ModuleForTests(expected.name, expected.variantName)

		// Check if the overrides field values are correctly aggregated.
		mod := variant.Module().(*AndroidApp)
		android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.overridableAppProperties.Overrides)
	}
}

func TestOverrideAndroidAppWithPrebuilt(t *testing.T) {
	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(
		t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
		}

		override_android_app {
			name: "bar",
			base: "foo",
		}

		android_app_import {
			name: "bar",
			prefer: true,
			apk: "bar.apk",
			presigned: true,
		}
		`)

	// An app that has an override that also has a prebuilt should not be hidden.
	foo := result.ModuleForTests("foo", "android_common")
	if foo.Module().IsHideFromMake() {
		t.Errorf("expected foo to have HideFromMake false")
	}

	// An override that also has a prebuilt should be hidden.
	barOverride := result.ModuleForTests("foo", "android_common_bar")
	if !barOverride.Module().IsHideFromMake() {
		t.Errorf("expected bar override variant of foo to have HideFromMake true")
	}
}

func TestOverrideAndroidAppStem(t *testing.T) {
	ctx, _ := testJava(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
		}
		override_android_app {
			name: "bar",
			base: "foo",
		}
		override_android_app {
			name: "baz",
			base: "foo",
			stem: "baz_stem",
		}
		android_app {
			name: "foo2",
			srcs: ["a.java"],
			sdk_version: "current",
			stem: "foo2_stem",
		}
		override_android_app {
			name: "bar2",
			base: "foo2",
		}
		override_android_app {
			name: "baz2",
			base: "foo2",
			stem: "baz2_stem",
		}
	`)
	for _, expected := range []struct {
		moduleName  string
		variantName string
		apkPath     string
	}{
		{
			moduleName:  "foo",
			variantName: "android_common",
			apkPath:     "out/soong/target/product/test_device/system/app/foo/foo.apk",
		},
		{
			moduleName:  "foo",
			variantName: "android_common_bar",
			apkPath:     "out/soong/target/product/test_device/system/app/bar/bar.apk",
		},
		{
			moduleName:  "foo",
			variantName: "android_common_baz",
			apkPath:     "out/soong/target/product/test_device/system/app/baz_stem/baz_stem.apk",
		},
		{
			moduleName:  "foo2",
			variantName: "android_common",
			apkPath:     "out/soong/target/product/test_device/system/app/foo2_stem/foo2_stem.apk",
		},
		{
			moduleName:  "foo2",
			variantName: "android_common_bar2",
			// Note that this may cause the duplicate output error.
			apkPath: "out/soong/target/product/test_device/system/app/foo2_stem/foo2_stem.apk",
		},
		{
			moduleName:  "foo2",
			variantName: "android_common_baz2",
			apkPath:     "out/soong/target/product/test_device/system/app/baz2_stem/baz2_stem.apk",
		},
	} {
		variant := ctx.ModuleForTests(expected.moduleName, expected.variantName)
		variant.Output(expected.apkPath)
	}
}

func TestOverrideAndroidAppDependency(t *testing.T) {
	ctx, _ := testJava(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
		}

		override_android_app {
			name: "bar",
			base: "foo",
			package_name: "org.dandroid.bp",
		}

		android_test {
			name: "baz",
			srcs: ["b.java"],
			instrumentation_for: "foo",
		}

		android_test {
			name: "qux",
			srcs: ["b.java"],
			instrumentation_for: "bar",
		}
		`)

	// Verify baz, which depends on the overridden module foo, has the correct classpath javac arg.
	javac := ctx.ModuleForTests("baz", "android_common").Rule("javac")
	fooTurbine := "out/soong/.intermediates/foo/android_common/turbine-combined/foo.jar"
	if !strings.Contains(javac.Args["classpath"], fooTurbine) {
		t.Errorf("baz classpath %v does not contain %q", javac.Args["classpath"], fooTurbine)
	}

	// Verify qux, which depends on the overriding module bar, has the correct classpath javac arg.
	javac = ctx.ModuleForTests("qux", "android_common").Rule("javac")
	barTurbine := "out/soong/.intermediates/foo/android_common_bar/turbine-combined/foo.jar"
	if !strings.Contains(javac.Args["classpath"], barTurbine) {
		t.Errorf("qux classpath %v does not contain %q", javac.Args["classpath"], barTurbine)
	}
}

func TestOverrideAndroidTest(t *testing.T) {
	ctx, _ := testJava(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			package_name: "com.android.foo",
			sdk_version: "current",
		}

		override_android_app {
			name: "bar",
			base: "foo",
			package_name: "com.android.bar",
		}

		android_test {
			name: "foo_test",
			srcs: ["b.java"],
			instrumentation_for: "foo",
		}

		override_android_test {
			name: "bar_test",
			base: "foo_test",
			package_name: "com.android.bar.test",
			instrumentation_for: "bar",
			instrumentation_target_package: "com.android.bar",
		}
		`)

	expectedVariants := []struct {
		moduleName        string
		variantName       string
		apkPath           string
		overrides         []string
		targetVariant     string
		packageFlag       string
		targetPackageFlag string
	}{
		{
			variantName:       "android_common",
			apkPath:           "/target/product/test_device/testcases/foo_test/arm64/foo_test.apk",
			overrides:         nil,
			targetVariant:     "android_common",
			packageFlag:       "",
			targetPackageFlag: "",
		},
		{
			variantName:       "android_common_bar_test",
			apkPath:           "/target/product/test_device/testcases/bar_test/arm64/bar_test.apk",
			overrides:         []string{"foo_test"},
			targetVariant:     "android_common_bar",
			packageFlag:       "com.android.bar.test",
			targetPackageFlag: "com.android.bar",
		},
	}
	for _, expected := range expectedVariants {
		variant := ctx.ModuleForTests("foo_test", expected.variantName)

		// Check the final apk name
		variant.Output("out/soong" + expected.apkPath)

		// Check if the overrides field values are correctly aggregated.
		mod := variant.Module().(*AndroidTest)
		if !reflect.DeepEqual(expected.overrides, mod.overridableAppProperties.Overrides) {
			t.Errorf("Incorrect overrides property value, expected: %q, got: %q",
				expected.overrides, mod.overridableAppProperties.Overrides)
		}

		// Check if javac classpath has the correct jar file path. This checks instrumentation_for overrides.
		javac := variant.Rule("javac")
		turbine := filepath.Join("out", "soong", ".intermediates", "foo", expected.targetVariant, "turbine-combined", "foo.jar")
		if !strings.Contains(javac.Args["classpath"], turbine) {
			t.Errorf("classpath %q does not contain %q", javac.Args["classpath"], turbine)
		}

		// Check aapt2 flags.
		res := variant.Output("package-res.apk")
		aapt2Flags := res.Args["flags"]
		checkAapt2LinkFlag(t, aapt2Flags, "rename-manifest-package", expected.packageFlag)
		checkAapt2LinkFlag(t, aapt2Flags, "rename-resources-package", expected.packageFlag)
		checkAapt2LinkFlag(t, aapt2Flags, "rename-instrumentation-target-package", expected.targetPackageFlag)
	}
}

func TestAndroidTest_FixTestConfig(t *testing.T) {
	ctx, _ := testJava(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			package_name: "com.android.foo",
			sdk_version: "current",
		}

		android_test {
			name: "foo_test",
			srcs: ["b.java"],
			instrumentation_for: "foo",
		}

		android_test {
			name: "bar_test",
			srcs: ["b.java"],
			package_name: "com.android.bar.test",
			instrumentation_for: "foo",
			mainline_package_name: "com.android.bar",
		}

		override_android_test {
			name: "baz_test",
			base: "foo_test",
			package_name: "com.android.baz.test",
			mainline_package_name: "com.android.baz",
		}
		`)

	testCases := []struct {
		moduleName    string
		variantName   string
		expectedFlags []string
	}{
		{
			moduleName:  "foo_test",
			variantName: "android_common",
		},
		{
			moduleName:  "bar_test",
			variantName: "android_common",
			expectedFlags: []string{
				"--manifest out/soong/.intermediates/bar_test/android_common/manifest_fixer/AndroidManifest.xml",
				"--package-name com.android.bar.test",
				"--mainline-package-name com.android.bar",
			},
		},
		{
			moduleName:  "foo_test",
			variantName: "android_common_baz_test",
			expectedFlags: []string{
				"--manifest out/soong/.intermediates/foo_test/android_common_baz_test/manifest_fixer/AndroidManifest.xml",
				"--package-name com.android.baz.test",
				"--test-file-name baz_test.apk",
				"out/soong/.intermediates/foo_test/android_common_baz_test/test_config_fixer/AndroidTest.xml",
				"--mainline-package-name com.android.baz",
			},
		},
	}

	for _, test := range testCases {
		variant := ctx.ModuleForTests(test.moduleName, test.variantName)
		params := variant.MaybeOutput("test_config_fixer/AndroidTest.xml")

		if len(test.expectedFlags) > 0 {
			if params.Rule == nil {
				t.Errorf("test_config_fixer was expected to run, but didn't")
			} else {
				for _, flag := range test.expectedFlags {
					if !strings.Contains(params.RuleParams.Command, flag) {
						t.Errorf("Flag %q was not found in command: %q", flag, params.RuleParams.Command)
					}
				}
			}
		} else {
			if params.Rule != nil {
				t.Errorf("test_config_fixer was not expected to run, but did: %q", params.RuleParams.Command)
			}
		}
	}
}

func TestInstrumentationTargetPrebuilt(t *testing.T) {
	bp := `
		android_app_import {
			name: "foo",
			apk: "foo.apk",
			presigned: true,
		}

		android_test {
			name: "bar",
			srcs: ["a.java"],
			instrumentation_for: "foo",
			sdk_version: "current",
		}
		`

	android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
	).ExtendWithErrorHandler(
		android.FixtureExpectsAtLeastOneErrorMatchingPattern(
			"instrumentation_for: dependency \"foo\" of type \"android_app_import\" does not provide JavaInfo so is unsuitable for use with this property")).
		RunTestWithBp(t, bp)
}

func TestStl(t *testing.T) {
	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
		cc_library {
			name: "libjni",
			sdk_version: "current",
			stl: "c++_shared",
		}

		android_test {
			name: "stl",
			jni_libs: ["libjni"],
			compile_multilib: "both",
			sdk_version: "current",
			stl: "c++_shared",
		}

		android_test {
			name: "system",
			jni_libs: ["libjni"],
			compile_multilib: "both",
			sdk_version: "current",
		}
		`)

	testCases := []struct {
		name string
		jnis []string
	}{
		{"stl",
			[]string{
				"libjni.so",
				"libc++_shared.so",
			},
		},
		{"system",
			[]string{
				"libjni.so",
			},
		},
	}

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			app := ctx.ModuleForTests(test.name, "android_common")
			jniLibZip := app.Output("jnilibs.zip")
			var jnis []string
			args := strings.Fields(jniLibZip.Args["jarArgs"])
			for i := 0; i < len(args); i++ {
				if args[i] == "-f" {
					jnis = append(jnis, args[i+1])
					i += 1
				}
			}
			jnisJoined := strings.Join(jnis, " ")
			for _, jni := range test.jnis {
				if !strings.Contains(jnisJoined, jni) {
					t.Errorf("missing jni %q in %q", jni, jnis)
				}
			}
		})
	}
}

func TestUsesLibraries(t *testing.T) {
	bp := `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			api_packages: ["foo"],
			sdk_version: "current",
		}

		java_sdk_library {
			name: "qux",
			srcs: ["a.java"],
			api_packages: ["qux"],
			sdk_version: "current",
		}

		java_sdk_library {
			name: "quuz",
			srcs: ["a.java"],
			api_packages: ["quuz"],
			sdk_version: "current",
		}

		java_sdk_library {
			name: "fred",
			srcs: ["a.java"],
			api_packages: ["fred"],
			sdk_version: "current",
		}

		java_sdk_library {
			name: "bar",
			srcs: ["a.java"],
			api_packages: ["bar"],
			sdk_version: "current",
		}

		java_sdk_library {
			name: "runtime-library",
			srcs: ["a.java"],
			sdk_version: "current",
		}

		java_library {
			name: "static-runtime-helper",
			srcs: ["a.java"],
			libs: ["runtime-library.impl"],
			sdk_version: "current",
		}

		java_library {
			name: "runtime-required-x",
			srcs: ["a.java"],
			installable: true,
			sdk_version: "current",
		}

		java_library {
			name: "runtime-optional-x",
			srcs: ["a.java"],
			installable: true,
			sdk_version: "current",
		}

		android_library {
			name: "static-x",
			uses_libs: ["runtime-required-x"],
			optional_uses_libs: ["runtime-optional-x"],
			sdk_version: "current",
		}

		java_library {
			name: "runtime-required-y",
			srcs: ["a.java"],
			installable: true,
			sdk_version: "current",
		}

		java_library {
			name: "runtime-optional-y",
			srcs: ["a.java"],
			installable: true,
			sdk_version: "current",
		}

		java_library {
			name: "static-y",
			srcs: ["a.java"],
			uses_libs: ["runtime-required-y"],
			optional_uses_libs: [
				"runtime-optional-y",
				"missing-lib-a",
			],
			sdk_version: "current",
		}

		// A library that has to use "provides_uses_lib", because:
		//    - it is not an SDK library
		//    - its library name is different from its module name
		java_library {
			name: "non-sdk-lib",
			provides_uses_lib: "com.non.sdk.lib",
			installable: true,
			srcs: ["a.java"],
		}

		android_app {
			name: "app",
			srcs: ["a.java"],
			libs: [
				"qux.impl",
				"quuz.stubs"
			],
			static_libs: [
				"static-runtime-helper",
				// statically linked component libraries should not pull their SDK libraries,
				// so "fred" should not be added to class loader context
				"fred.stubs",
				"static-x",
				"static-y",
			],
			uses_libs: [
				"foo",
				"non-sdk-lib"
			],
			sdk_version: "current",
			optional_uses_libs: [
				"bar",
				"missing-lib-b",
			],
		}

		android_app_import {
			name: "prebuilt",
			apk: "prebuilts/apk/app.apk",
			certificate: "platform",
			uses_libs: [
				"foo",
				"non-sdk-lib",
				"android.test.runner"
			],
			optional_uses_libs: [
				"bar",
				"missing-lib-b",
			],
		}
	`

	result := android.GroupFixturePreparers(
		prepareForJavaTest,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("runtime-library", "foo", "quuz", "qux", "bar", "fred"),
		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
			variables.BuildWarningBadOptionalUsesLibsAllowlist = []string{"app", "prebuilt"}
		}),
	).RunTestWithBp(t, bp)

	app := result.ModuleForTests("app", "android_common")
	prebuilt := result.ModuleForTests("prebuilt", "android_common")

	// Test that implicit dependencies on java_sdk_library instances are passed to the manifest.
	// These also include explicit `uses_libs`/`optional_uses_libs` entries, as they may be
	// propagated from dependencies.
	actualManifestFixerArgs := app.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
	expectManifestFixerArgs := `--extract-native-libs=true ` +
		`--uses-library foo ` +
		`--uses-library com.non.sdk.lib ` +
		`--uses-library qux ` +
		`--uses-library quuz ` +
		`--uses-library runtime-library ` +
		`--uses-library runtime-required-x ` +
		`--uses-library runtime-required-y ` +
		`--optional-uses-library bar ` +
		`--optional-uses-library runtime-optional-x ` +
		`--optional-uses-library runtime-optional-y`
	android.AssertStringDoesContain(t, "manifest_fixer args", actualManifestFixerArgs, expectManifestFixerArgs)

	// Test that all libraries are verified (library order matters).
	verifyCmd := app.Rule("verify_uses_libraries").RuleParams.Command
	verifyArgs := `--uses-library foo ` +
		`--uses-library com.non.sdk.lib ` +
		`--uses-library qux ` +
		`--uses-library quuz ` +
		`--uses-library runtime-library ` +
		`--uses-library runtime-required-x ` +
		`--uses-library runtime-required-y ` +
		`--optional-uses-library bar ` +
		`--optional-uses-library runtime-optional-x ` +
		`--optional-uses-library runtime-optional-y ` +
		`--missing-optional-uses-library missing-lib-b ` +
		`--missing-optional-uses-library missing-lib-a`
	android.AssertStringDoesContain(t, "verify cmd args", verifyCmd, verifyArgs)

	// Test that all libraries are verified for an APK (library order matters).
	verifyApkCmd := prebuilt.Rule("verify_uses_libraries").RuleParams.Command
	verifyApkArgs := `--uses-library foo ` +
		`--uses-library com.non.sdk.lib ` +
		`--uses-library android.test.runner ` +
		`--optional-uses-library bar ` +
		`--missing-optional-uses-library missing-lib-b `
	android.AssertStringDoesContain(t, "verify apk cmd args", verifyApkCmd, verifyApkArgs)

	// Test that necessary args are passed for constructing CLC in Ninja phase.
	cmd := app.Rule("dexpreopt").RuleParams.Command
	android.AssertStringDoesContain(t, "dexpreopt app cmd context", cmd, "--context-json=")
	android.AssertStringDoesContain(t, "dexpreopt app cmd product_packages", cmd,
		"--product-packages=out/soong/.intermediates/app/android_common/dexpreopt/app/product_packages.txt")
}

func TestDexpreoptBcp(t *testing.T) {
	bp := `
		java_sdk_library {
			name: "foo",
			srcs: ["a.java"],
			api_packages: ["foo"],
			sdk_version: "current",
		}

		java_sdk_library {
			name: "bar",
			srcs: ["a.java"],
			api_packages: ["bar"],
			permitted_packages: ["bar"],
			sdk_version: "current",
		}

		android_app {
			name: "app",
			srcs: ["a.java"],
			sdk_version: "current",
		}
	`

	testCases := []struct {
		name   string
		with   bool
		expect string
	}{
		{
			name:   "with updatable bcp",
			with:   true,
			expect: "/system/framework/foo.jar:/system/framework/bar.jar",
		},
		{
			name:   "without updatable bcp",
			with:   false,
			expect: "/system/framework/foo.jar",
		},
	}

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			result := android.GroupFixturePreparers(
				prepareForJavaTest,
				PrepareForTestWithJavaSdkLibraryFiles,
				FixtureWithLastReleaseApis("runtime-library", "foo", "bar"),
				dexpreopt.FixtureSetBootJars("platform:foo"),
				dexpreopt.FixtureSetApexBootJars("platform:bar"),
				dexpreopt.FixtureSetPreoptWithUpdatableBcp(test.with),
			).RunTestWithBp(t, bp)

			app := result.ModuleForTests("app", "android_common")
			cmd := app.Rule("dexpreopt").RuleParams.Command
			bcp := " -Xbootclasspath-locations:" + test.expect + " " // space at the end matters
			android.AssertStringDoesContain(t, "dexpreopt app bcp", cmd, bcp)
		})
	}
}

func TestCodelessApp(t *testing.T) {
	testCases := []struct {
		name   string
		bp     string
		noCode bool
	}{
		{
			name: "normal",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
				}
			`,
			noCode: false,
		},
		{
			name: "app without sources",
			bp: `
				android_app {
					name: "foo",
					sdk_version: "current",
				}
			`,
			noCode: true,
		},
		{
			name: "app with libraries",
			bp: `
				android_app {
					name: "foo",
					static_libs: ["lib"],
					sdk_version: "current",
				}

				java_library {
					name: "lib",
					srcs: ["a.java"],
					sdk_version: "current",
				}
			`,
			noCode: false,
		},
		{
			name: "app with sourceless libraries",
			bp: `
				android_app {
					name: "foo",
					static_libs: ["lib"],
					sdk_version: "current",
				}

				java_library {
					name: "lib",
					sdk_version: "current",
				}
			`,
			// TODO(jungjw): this should probably be true
			noCode: false,
		},
	}

	for _, test := range testCases {
		t.Run(test.name, func(t *testing.T) {
			ctx := testApp(t, test.bp)

			foo := ctx.ModuleForTests("foo", "android_common")
			manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
			if strings.Contains(manifestFixerArgs, "--has-no-code") != test.noCode {
				t.Errorf("unexpected manifest_fixer args: %q", manifestFixerArgs)
			}
		})
	}
}

func TestUncompressDex(t *testing.T) {
	testCases := []struct {
		name string
		bp   string

		uncompressedPlatform  bool
		uncompressedUnbundled bool
	}{
		{
			name: "normal",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
				}
			`,
			uncompressedPlatform:  true,
			uncompressedUnbundled: false,
		},
		{
			name: "use_embedded_dex",
			bp: `
				android_app {
					name: "foo",
					use_embedded_dex: true,
					srcs: ["a.java"],
					sdk_version: "current",
				}
			`,
			uncompressedPlatform:  true,
			uncompressedUnbundled: true,
		},
		{
			name: "privileged",
			bp: `
				android_app {
					name: "foo",
					privileged: true,
					srcs: ["a.java"],
					sdk_version: "current",
				}
			`,
			uncompressedPlatform:  true,
			uncompressedUnbundled: true,
		},
		{
			name: "normal_uncompress_dex_true",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
					uncompress_dex: true,
				}
			`,
			uncompressedPlatform:  true,
			uncompressedUnbundled: true,
		},
		{
			name: "normal_uncompress_dex_false",
			bp: `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					sdk_version: "current",
					uncompress_dex: false,
				}
			`,
			uncompressedPlatform:  false,
			uncompressedUnbundled: false,
		},
	}

	test := func(t *testing.T, bp string, want bool, unbundled bool) {
		t.Helper()

		result := android.GroupFixturePreparers(
			prepareForJavaTest,
			PrepareForTestWithPrebuiltsOfCurrentApi,
			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				if unbundled {
					variables.Unbundled_build = proptools.BoolPtr(true)
					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
				}
			}),
		).RunTestWithBp(t, bp)

		foo := result.ModuleForTests("foo", "android_common")
		dex := foo.Rule("r8")
		uncompressedInDexJar := strings.Contains(dex.Args["zipFlags"], "-L 0")
		aligned := foo.MaybeRule("zipalign").Rule != nil

		android.AssertBoolEquals(t, "uncompressed in dex", want, uncompressedInDexJar)

		android.AssertBoolEquals(t, "aligne", want, aligned)
	}

	for _, tt := range testCases {
		t.Run(tt.name, func(t *testing.T) {
			t.Run("platform", func(t *testing.T) {
				test(t, tt.bp, tt.uncompressedPlatform, false)
			})
			t.Run("unbundled", func(t *testing.T) {
				test(t, tt.bp, tt.uncompressedUnbundled, true)
			})
		})
	}
}

func checkAapt2LinkFlag(t *testing.T, aapt2Flags, flagName, expectedValue string) {
	if expectedValue != "" {
		expectedFlag := "--" + flagName + " " + expectedValue
		if !strings.Contains(aapt2Flags, expectedFlag) {
			t.Errorf("%q is missing in aapt2 link flags, %q", expectedFlag, aapt2Flags)
		}
	} else {
		unexpectedFlag := "--" + flagName
		if strings.Contains(aapt2Flags, unexpectedFlag) {
			t.Errorf("unexpected flag, %q is found in aapt2 link flags, %q", unexpectedFlag, aapt2Flags)
		}
	}
}

func TestExportedProguardFlagFiles(t *testing.T) {
	ctx, _ := testJava(t, `
		android_app {
			name: "foo",
			sdk_version: "current",
			static_libs: [
				"lib1",
				"lib3",
			],
		}

		android_library {
			name: "lib1",
			sdk_version: "current",
			optimize: {
				proguard_flags_files: ["lib1proguard.cfg"],
			},
			static_libs: ["lib2"],
		}

		android_library {
			name: "lib2",
			sdk_version: "current",
			optimize: {
				proguard_flags_files: ["lib2proguard.cfg"],
			}
		}

		android_library_import {
			name: "lib3",
			sdk_version: "current",
			aars: ["lib3.aar"],
			static_libs: ["lib4"],
		}

		android_library {
			name: "lib4",
			sdk_version: "current",
			optimize: {
				proguard_flags_files: ["lib4proguard.cfg"],
			}
		}


	`)

	m := ctx.ModuleForTests("foo", "android_common")
	r8 := m.Rule("java.r8")
	implicits := r8.Implicits.RelativeToTop().Strings()
	android.AssertStringListContains(t, "r8 implicits", implicits, "lib1proguard.cfg")
	android.AssertStringListContains(t, "r8 implicits", implicits, "lib2proguard.cfg")
	android.AssertStringListContains(t, "r8 implicits", implicits, "lib4proguard.cfg")
	android.AssertStringListContains(t, "r8 implicits", implicits, "out/soong/.intermediates/lib3/android_common/aar/proguard.txt")

	flags := r8.Args["r8Flags"]
	android.AssertStringDoesContain(t, "r8 flags", flags, "-include lib1proguard.cfg")
	android.AssertStringDoesContain(t, "r8 flags", flags, "-include lib2proguard.cfg")
	android.AssertStringDoesContain(t, "r8 flags", flags, "-include lib4proguard.cfg")
	android.AssertStringDoesContain(t, "r8 flags", flags, "-include out/soong/.intermediates/lib3/android_common/aar/proguard.txt")
}

func TestTargetSdkVersionManifestFixer(t *testing.T) {
	platform_sdk_codename := "Tiramisu"
	platform_sdk_version := 33
	testCases := []struct {
		name                     string
		targetSdkVersionInBp     string
		targetSdkVersionExpected string
		unbundledBuild           bool
		platformSdkFinal         bool
	}{
		{
			name:                     "Non-Unbundled build: Android.bp has targetSdkVersion",
			targetSdkVersionInBp:     "30",
			targetSdkVersionExpected: "30",
			unbundledBuild:           false,
		},
		{
			name:                     "Unbundled build: Android.bp has targetSdkVersion",
			targetSdkVersionInBp:     "30",
			targetSdkVersionExpected: "30",
			unbundledBuild:           true,
		},
		{
			name:                     "Non-Unbundled build: Android.bp has targetSdkVersion equal to platform_sdk_codename",
			targetSdkVersionInBp:     platform_sdk_codename,
			targetSdkVersionExpected: platform_sdk_codename,
			unbundledBuild:           false,
		},
		{
			name:                     "Unbundled build: Android.bp has targetSdkVersion equal to platform_sdk_codename",
			targetSdkVersionInBp:     platform_sdk_codename,
			targetSdkVersionExpected: "10000",
			unbundledBuild:           true,
		},

		{
			name:                     "Non-Unbundled build: Android.bp has no targetSdkVersion",
			targetSdkVersionExpected: platform_sdk_codename,
			unbundledBuild:           false,
		},
		{
			name:                     "Unbundled build: Android.bp has no targetSdkVersion",
			targetSdkVersionExpected: "10000",
			unbundledBuild:           true,
		},
		{
			name:                     "Bundled build in REL branches",
			targetSdkVersionExpected: "33",
			unbundledBuild:           false,
			platformSdkFinal:         true,
		},
	}
	for _, testCase := range testCases {
		targetSdkVersionTemplate := ""
		if testCase.targetSdkVersionInBp != "" {
			targetSdkVersionTemplate = fmt.Sprintf(`target_sdk_version: "%s",`, testCase.targetSdkVersionInBp)
		}
		bp := fmt.Sprintf(`
			android_app {
				name: "foo",
				sdk_version: "current",
				%s
			}
			`, targetSdkVersionTemplate)
		fixture := android.GroupFixturePreparers(
			prepareForJavaTest,
			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				if testCase.platformSdkFinal {
					variables.Platform_sdk_final = proptools.BoolPtr(true)
				}
				// explicitly set platform_sdk_codename to make the test deterministic
				variables.Platform_sdk_codename = &platform_sdk_codename
				variables.Platform_sdk_version = &platform_sdk_version
				variables.Platform_version_active_codenames = []string{platform_sdk_codename}
				// create a non-empty list if unbundledBuild==true
				if testCase.unbundledBuild {
					variables.Unbundled_build_apps = []string{"apex_a", "apex_b"}
				}
			}),
		)

		result := fixture.RunTestWithBp(t, bp)
		foo := result.ModuleForTests("foo", "android_common")

		manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
		android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
	}
}

func TestDefaultAppTargetSdkVersionForUpdatableModules(t *testing.T) {
	platform_sdk_codename := "Tiramisu"
	platform_sdk_version := 33
	testCases := []struct {
		name                     string
		platform_sdk_final       bool
		targetSdkVersionInBp     *string
		targetSdkVersionExpected *string
		updatable                bool
	}{
		{
			name:                     "Non-Updatable Module: Android.bp has older targetSdkVersion",
			targetSdkVersionInBp:     proptools.StringPtr("29"),
			targetSdkVersionExpected: proptools.StringPtr("29"),
			updatable:                false,
		},
		{
			name:                     "Updatable Module: Android.bp has older targetSdkVersion",
			targetSdkVersionInBp:     proptools.StringPtr("30"),
			targetSdkVersionExpected: proptools.StringPtr("30"),
			updatable:                true,
		},
		{
			name:                     "Updatable Module: Android.bp has no targetSdkVersion",
			targetSdkVersionExpected: proptools.StringPtr("10000"),
			updatable:                true,
		},
		{
			name:                     "[SDK finalised] Non-Updatable Module: Android.bp has older targetSdkVersion",
			platform_sdk_final:       true,
			targetSdkVersionInBp:     proptools.StringPtr("30"),
			targetSdkVersionExpected: proptools.StringPtr("30"),
			updatable:                false,
		},
		{
			name:                     "[SDK finalised] Updatable Module: Android.bp has older targetSdkVersion",
			platform_sdk_final:       true,
			targetSdkVersionInBp:     proptools.StringPtr("30"),
			targetSdkVersionExpected: proptools.StringPtr("30"),
			updatable:                true,
		},
		{
			name:                     "[SDK finalised] Updatable Module: Android.bp has targetSdkVersion as platform sdk codename",
			platform_sdk_final:       true,
			targetSdkVersionInBp:     proptools.StringPtr(platform_sdk_codename),
			targetSdkVersionExpected: proptools.StringPtr("33"),
			updatable:                true,
		},
		{
			name:                     "[SDK finalised] Updatable Module: Android.bp has no targetSdkVersion",
			platform_sdk_final:       true,
			targetSdkVersionExpected: proptools.StringPtr("33"),
			updatable:                true,
		},
	}
	for _, testCase := range testCases {
		targetSdkVersionTemplate := ""
		if testCase.targetSdkVersionInBp != nil {
			targetSdkVersionTemplate = fmt.Sprintf(`target_sdk_version: "%s",`, *testCase.targetSdkVersionInBp)
		}
		bp := fmt.Sprintf(`
			android_app {
				name: "foo",
				sdk_version: "current",
				min_sdk_version: "29",
				%s
				updatable: %t,
				enforce_default_target_sdk_version: %t
			}
			`, targetSdkVersionTemplate, testCase.updatable, testCase.updatable) // enforce default target sdk version if app is updatable

		fixture := android.GroupFixturePreparers(
			PrepareForTestWithJavaDefaultModules,
			android.PrepareForTestWithAllowMissingDependencies,
			android.PrepareForTestWithAndroidMk,
			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				// explicitly set following platform variables to make the test deterministic
				variables.Platform_sdk_final = &testCase.platform_sdk_final
				variables.Platform_sdk_version = &platform_sdk_version
				variables.Platform_sdk_codename = &platform_sdk_codename
				variables.Platform_version_active_codenames = []string{platform_sdk_codename}
				variables.Unbundled_build = proptools.BoolPtr(true)
				variables.Unbundled_build_apps = []string{"sampleModule"}
			}),
		)

		result := fixture.RunTestWithBp(t, bp)
		foo := result.ModuleForTests("foo", "android_common")

		manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
		android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+*testCase.targetSdkVersionExpected)
	}
}

func TestEnforceDefaultAppTargetSdkVersionFlag(t *testing.T) {
	platform_sdk_codename := "Tiramisu"
	platform_sdk_version := 33
	testCases := []struct {
		name                           string
		enforceDefaultTargetSdkVersion bool
		expectedError                  string
		platform_sdk_final             bool
		targetSdkVersionInBp           string
		targetSdkVersionExpected       string
		updatable                      bool
	}{
		{
			name:                           "Not enforcing Target SDK Version: Android.bp has older targetSdkVersion",
			enforceDefaultTargetSdkVersion: false,
			targetSdkVersionInBp:           "29",
			targetSdkVersionExpected:       "29",
			updatable:                      false,
		},
		{
			name:                           "[SDK finalised] Enforce Target SDK Version: Android.bp has current targetSdkVersion",
			enforceDefaultTargetSdkVersion: true,
			platform_sdk_final:             true,
			targetSdkVersionInBp:           "current",
			targetSdkVersionExpected:       "33",
			updatable:                      true,
		},
		{
			name:                           "Enforce Target SDK Version: Android.bp has current targetSdkVersion",
			enforceDefaultTargetSdkVersion: true,
			platform_sdk_final:             false,
			targetSdkVersionInBp:           "current",
			targetSdkVersionExpected:       "10000",
			updatable:                      false,
		},
		{
			name:                           "Not enforcing Target SDK Version for Updatable app",
			enforceDefaultTargetSdkVersion: false,
			expectedError:                  "Updatable apps must enforce default target sdk version",
			targetSdkVersionInBp:           "29",
			targetSdkVersionExpected:       "29",
			updatable:                      true,
		},
	}
	for _, testCase := range testCases {
		errExpected := testCase.expectedError != ""
		bp := fmt.Sprintf(`
			android_app {
				name: "foo",
				enforce_default_target_sdk_version: %t,
				sdk_version: "current",
				min_sdk_version: "29",
				target_sdk_version: "%v",
				updatable: %t
			}
			`, testCase.enforceDefaultTargetSdkVersion, testCase.targetSdkVersionInBp, testCase.updatable)

		fixture := android.GroupFixturePreparers(
			PrepareForTestWithJavaDefaultModules,
			android.PrepareForTestWithAllowMissingDependencies,
			android.PrepareForTestWithAndroidMk,
			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				// explicitly set following platform variables to make the test deterministic
				variables.Platform_sdk_final = &testCase.platform_sdk_final
				variables.Platform_sdk_version = &platform_sdk_version
				variables.Platform_sdk_codename = &platform_sdk_codename
				variables.Unbundled_build = proptools.BoolPtr(true)
				variables.Unbundled_build_apps = []string{"sampleModule"}
			}),
		)

		errorHandler := android.FixtureExpectsNoErrors
		if errExpected {
			errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(testCase.expectedError)
		}
		result := fixture.ExtendWithErrorHandler(errorHandler).RunTestWithBp(t, bp)

		if !errExpected {
			foo := result.ModuleForTests("foo", "android_common")
			manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
			android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
		}
	}
}

func TestEnforceDefaultAppTargetSdkVersionFlagForTests(t *testing.T) {
	platform_sdk_codename := "Tiramisu"
	platform_sdk_version := 33
	testCases := []struct {
		name                           string
		enforceDefaultTargetSdkVersion bool
		expectedError                  string
		platform_sdk_final             bool
		targetSdkVersionInBp           string
		targetSdkVersionExpected       string
	}{
		{
			name:                           "Not enforcing Target SDK Version: Android.bp has older targetSdkVersion",
			enforceDefaultTargetSdkVersion: false,
			targetSdkVersionInBp:           "29",
			targetSdkVersionExpected:       "29",
		},
		{
			name:                           "[SDK finalised] Enforce Target SDK Version: Android.bp has current targetSdkVersion",
			enforceDefaultTargetSdkVersion: true,
			platform_sdk_final:             true,
			targetSdkVersionInBp:           "current",
			targetSdkVersionExpected:       "33",
		},
		{
			name:                           "Enforce Target SDK Version: Android.bp has current targetSdkVersion",
			enforceDefaultTargetSdkVersion: true,
			platform_sdk_final:             false,
			targetSdkVersionInBp:           "current",
			targetSdkVersionExpected:       "10000",
		},
	}
	for _, testCase := range testCases {
		errExpected := testCase.expectedError != ""
		bp := fmt.Sprintf(`
			android_test {
				name: "foo",
				enforce_default_target_sdk_version: %t,
				min_sdk_version: "29",
				target_sdk_version: "%v",
			}
		`, testCase.enforceDefaultTargetSdkVersion, testCase.targetSdkVersionInBp)

		fixture := android.GroupFixturePreparers(
			PrepareForTestWithJavaDefaultModules,
			android.PrepareForTestWithAllowMissingDependencies,
			android.PrepareForTestWithAndroidMk,
			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				// explicitly set following platform variables to make the test deterministic
				variables.Platform_sdk_final = &testCase.platform_sdk_final
				variables.Platform_sdk_version = &platform_sdk_version
				variables.Platform_sdk_codename = &platform_sdk_codename
				variables.Unbundled_build = proptools.BoolPtr(true)
				variables.Unbundled_build_apps = []string{"sampleModule"}
			}),
		)

		errorHandler := android.FixtureExpectsNoErrors
		if errExpected {
			errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(testCase.expectedError)
		}
		result := fixture.ExtendWithErrorHandler(errorHandler).RunTestWithBp(t, bp)

		if !errExpected {
			foo := result.ModuleForTests("foo", "android_common")
			manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
			android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
		}
	}
}

func TestAppMissingCertificateAllowMissingDependencies(t *testing.T) {
	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
		android.PrepareForTestWithAllowMissingDependencies,
		android.PrepareForTestWithAndroidMk,
	).RunTestWithBp(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			certificate: ":missing_certificate",
			sdk_version: "current",
		}

		android_app {
			name: "bar",
			srcs: ["a.java"],
			certificate: ":missing_certificate",
			product_specific: true,
			sdk_version: "current",
		}`)

	foo := result.ModuleForTests("foo", "android_common")
	fooApk := foo.Output("foo.apk")
	if fooApk.Rule != android.ErrorRule {
		t.Fatalf("expected ErrorRule for foo.apk, got %s", fooApk.Rule.String())
	}
	android.AssertStringDoesContain(t, "expected error rule message", fooApk.Args["error"], "missing dependencies: missing_certificate\n")
}

func TestAppIncludesJniPackages(t *testing.T) {
	ctx := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
	).RunTestWithBp(t, `
		android_library_import {
			name: "aary-nodeps",
			aars: ["aary.aar"],
			extract_jni: true,
		}

		android_library {
			name: "aary-lib",
			sdk_version: "current",
			min_sdk_version: "21",
			static_libs: ["aary-nodeps"],
		}

		android_app {
			name: "aary-lib-dep",
			sdk_version: "current",
			min_sdk_version: "21",
			manifest: "AndroidManifest.xml",
			static_libs: ["aary-lib"],
			use_embedded_native_libs: true,
		}

		android_app {
			name: "aary-import-dep",
			sdk_version: "current",
			min_sdk_version: "21",
			manifest: "AndroidManifest.xml",
			static_libs: ["aary-nodeps"],
			use_embedded_native_libs: true,
		}

		android_app {
			name: "aary-no-use-embedded",
			sdk_version: "current",
			min_sdk_version: "21",
			manifest: "AndroidManifest.xml",
			static_libs: ["aary-nodeps"],
		}`)

	testCases := []struct {
		name       string
		hasPackage bool
	}{
		{
			name:       "aary-import-dep",
			hasPackage: true,
		},
		{
			name:       "aary-lib-dep",
			hasPackage: true,
		},
		{
			name:       "aary-no-use-embedded",
			hasPackage: false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			app := ctx.ModuleForTests(tc.name, "android_common")

			outputFile := "jnilibs.zip"
			jniOutputLibZip := app.MaybeOutput(outputFile)
			if jniOutputLibZip.Rule == nil && !tc.hasPackage {
				return
			}

			jniPackage := "arm64-v8a_jni.zip"
			inputs := jniOutputLibZip.Inputs
			foundPackage := false
			for i := 0; i < len(inputs); i++ {
				if strings.Contains(inputs[i].String(), jniPackage) {
					foundPackage = true
				}
			}
			if foundPackage != tc.hasPackage {
				t.Errorf("expected to find %v in %v inputs; inputs = %v", jniPackage, outputFile, inputs)
			}
		})
	}
}

func TestTargetSdkVersionMtsTests(t *testing.T) {
	platformSdkCodename := "Tiramisu"
	android_test := "android_test"
	android_test_helper_app := "android_test_helper_app"
	bpTemplate := `
	%v {
		name: "mytest",
		min_sdk_version: "34",
		target_sdk_version: "%v",
		test_suites: ["othersuite", "%v"],
	}
	`
	testCases := []struct {
		desc                     string
		moduleType               string
		targetSdkVersionInBp     string
		targetSdkVersionExpected string
		testSuites               string
	}{
		{
			desc:                     "Non-MTS android_test_apps targeting current should not be upgraded to 10000",
			moduleType:               android_test,
			targetSdkVersionInBp:     "current",
			targetSdkVersionExpected: platformSdkCodename,
			testSuites:               "non-mts-suite",
		},
		{
			desc:                     "MTS android_test_apps targeting released sdks should not be upgraded to 10000",
			moduleType:               android_test,
			targetSdkVersionInBp:     "29",
			targetSdkVersionExpected: "29",
			testSuites:               "mts-suite",
		},
		{
			desc:                     "MTS android_test_apps targeting current should be upgraded to 10000",
			moduleType:               android_test,
			targetSdkVersionInBp:     "current",
			targetSdkVersionExpected: "10000",
			testSuites:               "mts-suite",
		},
		{
			desc:                     "MTS android_test_helper_apps targeting current should be upgraded to 10000",
			moduleType:               android_test_helper_app,
			targetSdkVersionInBp:     "current",
			targetSdkVersionExpected: "10000",
			testSuites:               "mts-suite",
		},
	}
	fixture := android.GroupFixturePreparers(
		prepareForJavaTest,
		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
			variables.Platform_sdk_codename = &platformSdkCodename
			variables.Platform_version_active_codenames = []string{platformSdkCodename}
		}),
	)
	for _, testCase := range testCases {
		result := fixture.RunTestWithBp(t, fmt.Sprintf(bpTemplate, testCase.moduleType, testCase.targetSdkVersionInBp, testCase.testSuites))
		mytest := result.ModuleForTests("mytest", "android_common")
		manifestFixerArgs := mytest.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
		android.AssertStringDoesContain(t, testCase.desc, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
	}
}

func TestPrivappAllowlist(t *testing.T) {
	testJavaError(t, "privileged must be set in order to use privapp_allowlist", `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			privapp_allowlist: "perms.xml",
		}
	`)

	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(
		t,
		`
		android_app {
			name: "foo",
			srcs: ["a.java"],
			privapp_allowlist: "privapp_allowlist_com.android.foo.xml",
			privileged: true,
			sdk_version: "current",
		}
		override_android_app {
			name: "bar",
			base: "foo",
			package_name: "com.google.android.foo",
		}
		`,
	)
	app := result.ModuleForTests("foo", "android_common")
	overrideApp := result.ModuleForTests("foo", "android_common_bar")

	// verify that privapp allowlist is created for override apps
	overrideApp.Output("out/soong/.intermediates/foo/android_common_bar/privapp_allowlist_com.google.android.foo.xml")
	expectedAllowlistInput := "privapp_allowlist_com.android.foo.xml"
	overrideActualAllowlistInput := overrideApp.Rule("modifyAllowlist").Input.String()
	if expectedAllowlistInput != overrideActualAllowlistInput {
		t.Errorf("expected override allowlist to be %q; got %q", expectedAllowlistInput, overrideActualAllowlistInput)
	}

	// verify that permissions are copied to device
	app.Output("out/soong/target/product/test_device/system/etc/permissions/foo.xml")
	overrideApp.Output("out/soong/target/product/test_device/system/etc/permissions/bar.xml")
}

func TestPrivappAllowlistAndroidMk(t *testing.T) {
	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
		android.PrepareForTestWithAndroidMk,
	).RunTestWithBp(
		t,
		`
		android_app {
			name: "foo",
			srcs: ["a.java"],
			privapp_allowlist: "privapp_allowlist_com.android.foo.xml",
			privileged: true,
			sdk_version: "current",
		}
		override_android_app {
			name: "bar",
			base: "foo",
			package_name: "com.google.android.foo",
		}
		`,
	)
	baseApp := result.ModuleForTests("foo", "android_common")
	overrideApp := result.ModuleForTests("foo", "android_common_bar")

	baseAndroidApp := baseApp.Module().(*AndroidApp)
	baseEntries := android.AndroidMkEntriesForTest(t, result.TestContext, baseAndroidApp)[0]
	android.AssertStringMatches(
		t,
		"androidmk has incorrect LOCAL_SOONG_INSTALLED_MODULE; expected to find foo.apk",
		baseEntries.EntryMap["LOCAL_SOONG_INSTALLED_MODULE"][0],
		"\\S+foo.apk",
	)
	android.AssertStringMatches(
		t,
		"androidmk has incorrect LOCAL_SOONG_INSTALL_PAIRS; expected to it to include foo.apk",
		baseEntries.EntryMap["LOCAL_SOONG_INSTALL_PAIRS"][0],
		"\\S+foo.apk",
	)
	android.AssertStringMatches(
		t,
		"androidmk has incorrect LOCAL_SOONG_INSTALL_PAIRS; expected to it to include app",
		baseEntries.EntryMap["LOCAL_SOONG_INSTALL_PAIRS"][0],
		"\\S+foo.apk:\\S+/target/product/test_device/system/priv-app/foo/foo.apk",
	)
	android.AssertStringMatches(
		t,
		"androidmk has incorrect LOCAL_SOONG_INSTALL_PAIRS; expected to it to include privapp_allowlist",
		baseEntries.EntryMap["LOCAL_SOONG_INSTALL_PAIRS"][0],
		"privapp_allowlist_com.android.foo.xml:\\S+/target/product/test_device/system/etc/permissions/foo.xml",
	)

	overrideAndroidApp := overrideApp.Module().(*AndroidApp)
	overrideEntries := android.AndroidMkEntriesForTest(t, result.TestContext, overrideAndroidApp)[0]
	android.AssertStringMatches(
		t,
		"androidmk has incorrect LOCAL_SOONG_INSTALLED_MODULE; expected to find bar.apk",
		overrideEntries.EntryMap["LOCAL_SOONG_INSTALLED_MODULE"][0],
		"\\S+bar.apk",
	)
	android.AssertStringMatches(
		t,
		"androidmk has incorrect LOCAL_SOONG_INSTALL_PAIRS; expected to it to include bar.apk",
		overrideEntries.EntryMap["LOCAL_SOONG_INSTALL_PAIRS"][0],
		"\\S+bar.apk",
	)
	android.AssertStringMatches(
		t,
		"androidmk has incorrect LOCAL_SOONG_INSTALL_PAIRS; expected to it to include app",
		overrideEntries.EntryMap["LOCAL_SOONG_INSTALL_PAIRS"][0],
		"\\S+bar.apk:\\S+/target/product/test_device/system/priv-app/bar/bar.apk",
	)
	android.AssertStringMatches(
		t,
		"androidmk has incorrect LOCAL_SOONG_INSTALL_PAIRS; expected to it to include privapp_allowlist",
		overrideEntries.EntryMap["LOCAL_SOONG_INSTALL_PAIRS"][0],
		"\\S+soong/.intermediates/foo/android_common_bar/privapp_allowlist_com.google.android.foo.xml:\\S+/target/product/test_device/system/etc/permissions/bar.xml",
	)
}

func TestAppFlagsPackages(t *testing.T) {
	ctx := android.GroupFixturePreparers(
		prepareForJavaTest,
		android.FixtureMergeMockFs(
			map[string][]byte{
				"res/layout/layout.xml":         nil,
				"res/values/strings.xml":        nil,
				"res/values-en-rUS/strings.xml": nil,
			},
		),
	).RunTestWithBp(t, `
		android_app {
			name: "foo",
			srcs: ["a.java"],
			sdk_version: "current",
			flags_packages: [
				"bar",
				"baz",
			],
		}
		aconfig_declarations {
			name: "bar",
			package: "com.example.package.bar",
			container: "com.android.foo",
			srcs: [
				"bar.aconfig",
			],
		}
		aconfig_declarations {
			name: "baz",
			package: "com.example.package.baz",
			container: "com.android.foo",
			srcs: [
				"baz.aconfig",
			],
		}
	`)

	foo := ctx.ModuleForTests("foo", "android_common")

	// android_app module depends on aconfig_declarations listed in flags_packages
	android.AssertBoolEquals(t, "foo expected to depend on bar", true,
		CheckModuleHasDependency(t, ctx.TestContext, "foo", "android_common", "bar"))

	android.AssertBoolEquals(t, "foo expected to depend on baz", true,
		CheckModuleHasDependency(t, ctx.TestContext, "foo", "android_common", "baz"))

	aapt2LinkRule := foo.Rule("android/soong/java.aapt2Link")
	linkInFlags := aapt2LinkRule.Args["inFlags"]
	android.AssertStringDoesContain(t,
		"aapt2 link command expected to pass feature flags arguments",
		linkInFlags,
		"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
	)

	aapt2CompileRule := foo.Rule("android/soong/java.aapt2Compile")
	compileFlags := aapt2CompileRule.Args["cFlags"]
	android.AssertStringDoesContain(t,
		"aapt2 compile command expected to pass feature flags arguments",
		compileFlags,
		"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
	)
}

func TestAppFlagsPackagesPropagation(t *testing.T) {
	ctx := testApp(t, `
		aconfig_declarations {
			name: "foo",
			package: "com.example.package.foo",
			container: "com.android.foo",
			srcs: [
				"foo.aconfig",
			],
		}
		aconfig_declarations {
			name: "bar",
			package: "com.example.package.bar",
			container: "com.android.bar",
			srcs: [
				"bar.aconfig",
			],
		}
		aconfig_declarations {
			name: "baz",
			package: "com.example.package.baz",
			container: "com.android.baz",
			srcs: [
				"baz.aconfig",
			],
		}
		android_library {
			name: "foo_lib",
			srcs: ["a.java"],
			sdk_version: "current",
			flags_packages: [
				"foo",
			],
		}
		android_library {
			name: "bar_lib",
			srcs: ["a.java"],
			sdk_version: "current",
			flags_packages: [
				"bar",
			],
		}
		android_app {
			name: "baz_app",
			srcs: ["a.java"],
			sdk_version: "current",
			flags_packages: [
				"baz",
			],
			static_libs: [
				"bar_lib",
			],
			libs: [
				"foo_lib",
			],
		}
	`)

	bazApp := ctx.ModuleForTests("baz_app", "android_common")

	// android_app module depends on aconfig_declarations listed in flags_packages
	// and that of static libs, but not libs
	aapt2LinkRule := bazApp.Rule("android/soong/java.aapt2Link")
	linkInFlags := aapt2LinkRule.Args["inFlags"]
	android.AssertStringDoesContain(t,
		"aapt2 link command expected to pass feature flags arguments of flags_packages and that of its static libs",
		linkInFlags,
		"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
	)
	android.AssertStringDoesNotContain(t,
		"aapt2 link command expected to not pass feature flags arguments of flags_packages of its libs",
		linkInFlags,
		"--feature-flags @out/soong/.intermediates/foo/intermediate.txt",
	)
}

// Test that dexpreopt is disabled if an optional_uses_libs exists, but does not provide an implementation.
func TestNoDexpreoptOptionalUsesLibDoesNotHaveImpl(t *testing.T) {
	bp := `
		java_sdk_library_import {
			name: "sdklib_noimpl",
			public: {
				jars: ["stub.jar"],
			},
		}
		android_app {
			name: "app",
			srcs: ["a.java"],
			sdk_version: "current",
			optional_uses_libs: [
				"sdklib_noimpl",
			],
		}
	`
	result := prepareForJavaTest.RunTestWithBp(t, bp)
	dexpreopt := result.ModuleForTests("app", "android_common").MaybeRule("dexpreopt").Rule
	android.AssertBoolEquals(t, "dexpreopt should be disabled if optional_uses_libs does not have an implementation", true, dexpreopt == nil)
}

func TestTestOnlyApp(t *testing.T) {
	t.Parallel()
	ctx := android.GroupFixturePreparers(
		prepareForJavaTest,
	).RunTestWithBp(t, `
                // These should be test-only
                android_test {
                        name: "android-test",
                }
                android_test_helper_app {
                        name: "helper-app",
                }
                override_android_test {
                        name: "override-test",
                        base: "android-app",
                }
                // And these should not be
		android_app {
			name: "android-app",
                        srcs: ["b.java"],
			sdk_version: "current",
		}
	`)

	expectedTestOnly := []string{
		"android-test",
		"helper-app",
		"override-test",
	}

	expectedTopLevel := []string{
		"android-test",
		"override-test",
	}

	assertTestOnlyAndTopLevel(t, ctx, expectedTestOnly, expectedTopLevel)
}

func TestAppStem(t *testing.T) {
	ctx := testApp(t, `
				android_app {
					name: "foo",
					srcs: ["a.java"],
					stem: "foo-new",
					sdk_version: "current",
				}`)

	foo := ctx.ModuleForTests("foo", "android_common")

	outputs := fmt.Sprint(foo.AllOutputs())
	if !strings.Contains(outputs, "foo-new.apk") {
		t.Errorf("Module output does not contain expected apk %s", "foo-new.apk")
	}
}

func TestAppMinSdkVersionOverride(t *testing.T) {
	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
	).RunTestWithBp(t, `
		android_app {
			name: "com.android.foo",
			srcs: ["a.java"],
			sdk_version: "current",
			min_sdk_version: "31",
			updatable: true,
		}
		override_android_app {
			name: "com.android.go.foo",
			base: "com.android.foo",
			min_sdk_version: "33",
		}
	`)
	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
	fooOverride := result.ModuleForTests("com.android.foo", "android_common_com.android.go.foo").Rule("manifestFixer")

	android.AssertStringDoesContain(t,
		"com.android.foo: expected manifest fixer to set minSdkVersion to T",
		foo.BuildParams.Args["args"],
		"--minSdkVersion  31",
	)
	android.AssertStringDoesContain(t,
		"com.android.go.foo: expected manifest fixer to set minSdkVersion to T",
		fooOverride.BuildParams.Args["args"],
		"--minSdkVersion  33",
	)

}

func TestNotApplyDefaultUpdatableModuleVersion(t *testing.T) {
	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
	).RunTestWithBp(t, `
		android_app {
			name: "com.android.foo",
			srcs: ["a.java"],
			sdk_version: "current",
			min_sdk_version: "31",
		}
	`)
	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
	android.AssertStringDoesNotContain(t,
		"com.android.foo: expected manifest fixer to not set override-placeholder-version",
		foo.BuildParams.Args["args"],
		"--override-placeholder-version",
	)
}

func TestNotApplyOverrideApexManifestDefaultVersion(t *testing.T) {
	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
		android.FixtureMergeEnv(map[string]string{
			"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION": "1234",
		}),
	).RunTestWithBp(t, `
		android_app {
			name: "com.android.foo",
			srcs: ["a.java"],
			sdk_version: "current",
			min_sdk_version: "31",
		}
	`)
	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
	android.AssertStringDoesNotContain(t,
		"com.android.foo: expected manifest fixer to not set override-placeholder-version",
		foo.BuildParams.Args["args"],
		"--override-placeholder-version",
	)
}

func TestResourcesWithFlagDirectories(t *testing.T) {
	result := android.GroupFixturePreparers(
		PrepareForTestWithJavaDefaultModules,
		android.FixtureMergeMockFs(android.MockFS{
			"res/flag(test.package.flag1)/values/bools.xml":                          nil,
			"res/flag(!test.package.flag2)/values/bools.xml":                         nil,
			"res/flag(test.package.flag1)/values-config/strings_google_services.xml": nil,
			"res/flags(test.package.flag1)/values/strings.xml":                       nil,
		}),
	).RunTestWithBp(t, `
		android_library {
			name: "foo",
			srcs: ["a.java"],
			use_resource_processor: true,
			resource_dirs: [
				"res",
			],
		}
	`)
	fooModule := result.ModuleForTests("foo", "android_common")
	compileOutputPaths := fooModule.Rule("aapt2Compile").Outputs.Strings()

	android.AssertStringListContains(
		t,
		"Expected to generate flag path",
		compileOutputPaths,
		"out/soong/.intermediates/foo/android_common/aapt2/res/values_bools.(test.package.flag1).arsc.flat",
	)
	android.AssertStringListContains(
		t,
		"Expected to generate flag path with ! prefix in name",
		compileOutputPaths,
		"out/soong/.intermediates/foo/android_common/aapt2/res/values_bools.(!test.package.flag2).arsc.flat",
	)
	android.AssertStringListContains(
		t,
		"Expected to generate flag path with configs",
		compileOutputPaths,
		"out/soong/.intermediates/foo/android_common/aapt2/res/values-config_strings_google_services.(test.package.flag1).arsc.flat",
	)
	android.AssertStringListDoesNotContain(
		t,
		"Expected to not generate flag path with non-flag(flag_name) pattern",
		compileOutputPaths,
		"out/soong/.intermediates/foo/android_common/aapt2/res/values_strings.(test.package.flag1).arsc.flat",
	)
}

func TestAutogeneratedStaticRro(t *testing.T) {
	t.Parallel()
	bp := `
android_app {
	name: "foo",
	srcs: ["foo.java"],
	platform_apis: true,
}
override_android_app {
	name: "override_foo",
	base: "foo",
}
`
	testCases := []struct {
		desc               string
		preparer           android.FixturePreparer
		overlayApkExpected bool
	}{
		{
			desc:               "No DEVICE_PACKAGE_OVERLAYS, no overlay .apk file",
			overlayApkExpected: false,
		},
		{
			desc:               "DEVICE_PACKAGE_OVERLAYS exists, but the directory is empty",
			overlayApkExpected: false,
			preparer: android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				variables.DeviceResourceOverlays = []string{"device/company/test_product"}
			}),
		},
		{
			desc:               "DEVICE_PACKAGE_OVERLAYS exists, directory is non-empty, but does not contain a matching resource dir",
			overlayApkExpected: false,
			preparer: android.GroupFixturePreparers(
				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
					variables.DeviceResourceOverlays = []string{"device/company/test_product"}
				}),
				android.MockFS{
					"res/foo.xml": nil,
					"device/company/test_product/different_res/foo.xml": nil, // different dir
				}.AddToFixture(),
			),
		},
		{
			desc:               "DEVICE_PACKAGE_OVERLAYS and the directory contain a matching resource dir",
			overlayApkExpected: true,
			preparer: android.GroupFixturePreparers(
				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
					variables.DeviceResourceOverlays = []string{"device/company/test_product"}
				}),
				android.MockFS{
					"res/foo.xml": nil,
					"device/company/test_product/res/foo.xml": nil,
				}.AddToFixture(),
			),
		},
	}
	for _, tc := range testCases {
		result := android.GroupFixturePreparers(
			PrepareForTestWithJavaDefaultModules,
			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				variables.EnforceRROTargets = []string{"*"}
			}),
			android.OptionalFixturePreparer(tc.preparer),
		).RunTestWithBp(t, bp)
		vendorOverlayApk := result.ModuleForTests("foo__test_product__auto_generated_rro_vendor", "android_arm64_armv8-a").MaybeOutput("foo__test_product__auto_generated_rro_vendor.apk")
		android.AssertBoolEquals(t, tc.desc, tc.overlayApkExpected, vendorOverlayApk.Rule != nil)
		overrideVendorOverlayApk := result.ModuleForTests("override_foo__test_product__auto_generated_rro_vendor", "android_arm64_armv8-a").MaybeOutput("override_foo__test_product__auto_generated_rro_vendor.apk")
		android.AssertBoolEquals(t, tc.desc, tc.overlayApkExpected, overrideVendorOverlayApk.Rule != nil)
	}
}

func TestNoAutogeneratedStaticRroForDisabledOverrideApps(t *testing.T) {
	t.Parallel()
	bp := `
soong_config_module_type {
	name: "my_custom_override_android_app",
	module_type: "override_android_app",
	config_namespace: "my_namespace",
	value_variables: ["my_app_enabled"],
	properties: ["enabled"],
}
soong_config_bool_variable {
	name: "my_app_enabled",
}
android_app {
	name: "foo",
	srcs: ["foo.java"],
	platform_apis: true,
}
my_custom_override_android_app {
	name: "override_foo",
	base: "foo",
	soong_config_variables: {
		my_app_enabled: {
			enabled: true,
			conditions_default: {
				enabled: false
			},
		},
	}
}
`
	testCases := []struct {
		desc               string
		preparer           android.FixturePreparer
		overlayApkExpected bool
	}{
		{
			desc:               "my_app_enabled is empty",
			overlayApkExpected: false,
		},
		{
			desc:               "my_app_enabled is true",
			overlayApkExpected: true,
			preparer: android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				variables.VendorVars = map[string]map[string]string{
					"my_namespace": {
						"my_app_enabled": "true",
					},
				}
			}),
		},
	}
	for _, tc := range testCases {
		result := android.GroupFixturePreparers(
			PrepareForTestWithJavaDefaultModules,
			android.PrepareForTestWithSoongConfigModuleBuildComponents,
			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				variables.EnforceRROTargets = []string{"*"}
			}),
			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
				variables.DeviceResourceOverlays = []string{"device/company/test_product"}
			}),
			android.MockFS{
				"res/foo.xml": nil,
				"device/company/test_product/res/foo.xml": nil,
			}.AddToFixture(),
			android.OptionalFixturePreparer(tc.preparer),
		).RunTestWithBp(t, bp)
		overrideVendorOverlayApk := result.ModuleForTests("override_foo__test_product__auto_generated_rro_vendor", "android_arm64_armv8-a").Module().(*AutogenRuntimeResourceOverlay)
		android.AssertBoolEquals(t, tc.desc, tc.overlayApkExpected, overrideVendorOverlayApk.exportPackage != nil)
	}
}
