Implement DPI variants in android_app_import.

Bug: 128610294
Test: app_test.go
Change-Id: Ie3e558bfdb40de6b0b9df95d3b373d08a4084d7b
diff --git a/java/app.go b/java/app.go
index ec021fc..849af5b 100644
--- a/java/app.go
+++ b/java/app.go
@@ -17,17 +17,20 @@
 // This file contains the module types for compiling Android apps.
 
 import (
-	"path/filepath"
-	"strings"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
+	"path/filepath"
+	"reflect"
+	"strings"
 
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/tradefed"
 )
 
+var supportedDpis = [...]string{"Ldpi", "Mdpi", "Hdpi", "Xhdpi", "Xxhdpi", "Xxxhdpi"}
+var dpiVariantsStruct reflect.Type
+
 func init() {
 	android.RegisterModuleType("android_app", AndroidAppFactory)
 	android.RegisterModuleType("android_test", AndroidTestFactory)
@@ -35,6 +38,22 @@
 	android.RegisterModuleType("android_app_certificate", AndroidAppCertificateFactory)
 	android.RegisterModuleType("override_android_app", OverrideAndroidAppModuleFactory)
 	android.RegisterModuleType("android_app_import", AndroidAppImportFactory)
+
+	// Dynamically construct a struct for the dpi_variants property in android_app_import.
+	perDpiStruct := reflect.StructOf([]reflect.StructField{
+		{
+			Name: "Apk",
+			Type: reflect.TypeOf((*string)(nil)),
+		},
+	})
+	dpiVariantsFields := make([]reflect.StructField, len(supportedDpis))
+	for i, dpi := range supportedDpis {
+		dpiVariantsFields[i] = reflect.StructField{
+			Name: string(dpi),
+			Type: perDpiStruct,
+		}
+	}
+	dpiVariantsStruct = reflect.StructOf(dpiVariantsFields)
 }
 
 // AndroidManifest.xml merging
@@ -635,6 +654,26 @@
 	// A prebuilt apk to import
 	Apk string
 
+	// Per-DPI settings. This property makes it possible to specify a different source apk path for
+	// each DPI.
+	//
+	// Example:
+	//
+	//     android_app_import {
+	//         name: "example_import",
+	//         apk: "prebuilts/example.apk",
+	//         dpi_variants: {
+	//             mdpi: {
+	//                 apk: "prebuilts/example_mdpi.apk",
+	//             },
+	//             xhdpi: {
+	//                 apk: "prebuilts/example_xhdpi.apk",
+	//             },
+	//         },
+	//         certificate: "PRESIGNED",
+	//     }
+	Dpi_variants interface{}
+
 	// The name of a certificate in the default certificate directory, blank to use the default
 	// product certificate, or an android_app_certificate module name in the form ":module".
 	Certificate *string
@@ -656,6 +695,41 @@
 	Overrides []string
 }
 
+func getApkPathForDpi(dpiVariantsValue reflect.Value, dpi string) string {
+	dpiField := dpiVariantsValue.FieldByName(proptools.FieldNameForProperty(dpi))
+	if !dpiField.IsValid() {
+		return ""
+	}
+	apkValue := dpiField.FieldByName("Apk").Elem()
+	if apkValue.IsValid() {
+		return apkValue.String()
+	}
+	return ""
+}
+
+// Chooses a source APK path to use based on the module's per-DPI settings and the product config.
+func (a *AndroidAppImport) getSrcApkPath(ctx android.ModuleContext) string {
+	config := ctx.Config()
+	dpiVariantsValue := reflect.ValueOf(a.properties.Dpi_variants).Elem()
+	if !dpiVariantsValue.IsValid() {
+		return a.properties.Apk
+	}
+	// Match PRODUCT_AAPT_PREF_CONFIG first and then PRODUCT_AAPT_PREBUILT_DPI.
+	if config.ProductAAPTPreferredConfig() != "" {
+		if apk := getApkPathForDpi(dpiVariantsValue, config.ProductAAPTPreferredConfig()); apk != "" {
+			return apk
+		}
+	}
+	for _, dpi := range config.ProductAAPTPrebuiltDPI() {
+		if apk := getApkPathForDpi(dpiVariantsValue, dpi); apk != "" {
+			return apk
+		}
+	}
+
+	// No match. Use the generic one.
+	return a.properties.Apk
+}
+
 func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) {
 	cert := android.SrcIsModule(String(a.properties.Certificate))
 	if cert != "" {
@@ -701,10 +775,9 @@
 	_, certificates := collectAppDeps(ctx)
 
 	// TODO: LOCAL_EXTRACT_APK/LOCAL_EXTRACT_DPI_APK
-	// TODO: LOCAL_DPI_VARIANTS
 	// TODO: LOCAL_PACKAGE_SPLITS
 
-	srcApk := a.prebuilt.SingleSourcePath(ctx)
+	srcApk := android.PathForModuleSrc(ctx, a.getSrcApkPath(ctx))
 
 	// TODO: Install or embed JNI libraries
 
@@ -754,6 +827,7 @@
 // android_app_import imports a prebuilt apk with additional processing specified in the module.
 func AndroidAppImportFactory() android.Module {
 	module := &AndroidAppImport{}
+	module.properties.Dpi_variants = reflect.New(dpiVariantsStruct).Interface()
 	module.AddProperties(&module.properties)
 	module.AddProperties(&module.dexpreoptProperties)