Ensure tracing of config specific xml files

The current version only searches the last added configuration.

Change-Id: Icd8e8b517dfe52f409fabf594660552f242b2012
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
index 7874b5c..7a82e3f 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
@@ -37,6 +37,8 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -48,8 +50,8 @@
 
 public class LegacyResourceShrinker {
   private final Map<String, byte[]> dexInputs;
-  private final List<PathAndBytes> resFolderInputs;
-  private final List<PathAndBytes> xmlInputs;
+  private final Collection<PathAndBytes> resFolderInputs;
+  private final Collection<PathAndBytes> xmlInputs;
   private List<String> proguardMapStrings;
   private final List<PathAndBytes> manifest;
   private final Map<PathAndBytes, FeatureSplit> resourceTables;
@@ -57,8 +59,8 @@
   public static class Builder {
 
     private final Map<String, byte[]> dexInputs = new HashMap<>();
-    private final List<PathAndBytes> resFolderInputs = new ArrayList<>();
-    private final List<PathAndBytes> xmlInputs = new ArrayList<>();
+    private final Map<Path, PathAndBytes> resFolderInputs = new HashMap<>();
+    private final Map<Path, PathAndBytes> xmlInputs = new HashMap<>();
 
     private final List<PathAndBytes> manifests = new ArrayList<>();
     private final Map<PathAndBytes, FeatureSplit> resourceTables = new HashMap<>();
@@ -88,19 +90,34 @@
     }
 
     public Builder addResFolderInput(Path path, byte[] bytes) {
-      resFolderInputs.add(new PathAndBytes(bytes, path));
+      PathAndBytes existing = resFolderInputs.get(path);
+      if (existing != null) {
+        assert Arrays.equals(existing.getBytes(), bytes);
+      } else {
+        resFolderInputs.put(path, new PathAndBytes(bytes, path));
+      }
       return this;
     }
 
     public Builder addXmlInput(Path path, byte[] bytes) {
-      xmlInputs.add(new PathAndBytes(bytes, path));
+      PathAndBytes existing = xmlInputs.get(path);
+      if (existing != null) {
+        assert Arrays.equals(existing.getBytes(), bytes);
+      } else {
+        xmlInputs.put(path, new PathAndBytes(bytes, path));
+      }
       return this;
     }
 
     public LegacyResourceShrinker build() {
       assert manifests != null && resourceTables != null;
       return new LegacyResourceShrinker(
-          dexInputs, resFolderInputs, manifests, resourceTables, xmlInputs, proguardMapStrings);
+          dexInputs,
+          resFolderInputs.values(),
+          manifests,
+          resourceTables,
+          xmlInputs.values(),
+          proguardMapStrings);
     }
 
     public void setProguardMapStrings(List<String> proguardMapStrings) {
@@ -110,10 +127,10 @@
 
   private LegacyResourceShrinker(
       Map<String, byte[]> dexInputs,
-      List<PathAndBytes> resFolderInputs,
+      Collection<PathAndBytes> resFolderInputs,
       List<PathAndBytes> manifests,
       Map<PathAndBytes, FeatureSplit> resourceTables,
-      List<PathAndBytes> xmlInputs,
+      Collection<PathAndBytes> xmlInputs,
       List<String> proguardMapStrings) {
     this.dexInputs = dexInputs;
     this.resFolderInputs = resFolderInputs;
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
index f289319..49f3546 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -59,7 +59,7 @@
   private final Map<String, Supplier<InputStream>> resfileProviders = new HashMap<>();
   private final Map<ResourceTable, FeatureSplit> resourceTables = new HashMap<>();
   private ClassReferenceCallback enqueuerCallback;
-  private Map<Integer, String> resourceIdToXmlFiles;
+  private Map<Integer, List<String>> resourceIdToXmlFiles;
   private Set<String> packageNames;
   private final Set<String> seenNoneClassValues = new HashSet<>();
   private final Set<Integer> seenResourceIds = new HashSet<>();
@@ -208,10 +208,12 @@
   }
 
   private void traceXmlForResourceId(int id) {
-    String xmlFile = getResourceIdToXmlFiles().get(id);
-    if (xmlFile != null) {
-      InputStream inputStream = xmlFileProviders.get(xmlFile).get();
-      traceXml(xmlFile, inputStream);
+    List<String> xmlFiles = getResourceIdToXmlFiles().get(id);
+    if (xmlFiles != null) {
+      for (String xmlFile : xmlFiles) {
+        InputStream inputStream = xmlFileProviders.get(xmlFile).get();
+        traceXml(xmlFile, inputStream);
+      }
     }
   }
 
@@ -255,7 +257,7 @@
     element.getChildList().forEach(e -> visitNode(e, xmlName));
   }
 
-  public Map<Integer, String> getResourceIdToXmlFiles() {
+  public Map<Integer, List<String>> getResourceIdToXmlFiles() {
     if (resourceIdToXmlFiles == null) {
       resourceIdToXmlFiles = new HashMap<>();
       for (ResourceTable resourceTable : resourceTables.keySet()) {
@@ -271,7 +273,9 @@
                       FileReference file = item.getFile();
                       if (file.getType() == FileReference.Type.PROTO_XML) {
                         int id = ResourceTableUtilKt.toIdentifier(packageEntry, type, entry);
-                        resourceIdToXmlFiles.put(id, file.getPath());
+                        resourceIdToXmlFiles
+                            .computeIfAbsent(id, unused -> new ArrayList<>())
+                            .add(file.getPath());
                       }
                     }
                   }
diff --git a/src/test/java/com/android/tools/r8/androidresources/MultiConfigXmlFilesTest.java b/src/test/java/com/android/tools/r8/androidresources/MultiConfigXmlFilesTest.java
new file mode 100644
index 0000000..c8ebee4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/MultiConfigXmlFilesTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.androidresources;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MultiConfigXmlFilesTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+  }
+
+  public static String VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE =
+      "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" class=\"%s\"/>\n";
+
+  public AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withSimpleManifestAndAppNameString()
+        .addRClassInitializeWithDefaultValues(R.xml.class)
+        // We add two configurations of the xml file, one for default and one for -v24.
+        // They reference different classes, both of which we should keep.
+        .addXml(
+            "xml_with_class_reference.xml",
+            String.format(VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE, Foo.class.getTypeName()))
+        .addApiSpecificXml(
+            "xml_with_class_reference.xml",
+            String.format(VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE, Bar.class.getTypeName()))
+        .build(temp);
+  }
+
+  @Test
+  public void testClassReferences() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addProgramClasses(TestClass.class, Foo.class, Bar.class)
+        .addAndroidResources(getTestResources(temp))
+        .addKeepMainRule(TestClass.class)
+        .enableOptimizedShrinking()
+        .compile()
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName(
+                  "xml", "xml_with_class_reference");
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(Foo.class), isPresent());
+              assertThat(codeInspector.clazz(Bar.class), isPresent());
+            })
+        .assertSuccess();
+  }
+
+  public static class TestClass {
+    public static void main(String[] args) {
+      System.out.println(R.xml.xml_with_class_reference);
+    }
+  }
+
+  // Only referenced from XML file
+  public static class Bar {}
+
+  // Only referenced from XML file
+  public static class Foo {}
+
+  public static class R {
+    public static class xml {
+      public static int xml_with_class_reference;
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index eb5b8e2..80333c3 100644
--- a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -293,6 +293,8 @@
     private final Map<String, Integer> styleables = new TreeMap<>();
     private final Map<String, byte[]> drawables = new TreeMap<>();
     private final Map<String, String> xmlFiles = new TreeMap<>();
+    // Used for xml files that we hard code to -v24
+    private final Map<String, String> apiSpecificXmlFiles = new TreeMap<>();
     private final Map<String, String> rawXmlFiles = new TreeMap<>();
     private final List<Class<?>> classesToRemap = new ArrayList<>();
     private int packageId = 0x7f;
@@ -304,8 +306,15 @@
     // These R classes will be used to rewrite the namespace and class names on the aapt2
     // generated names.
     public AndroidTestResourceBuilder addRClassInitializeWithDefaultValues(Class<?>... rClasses) {
+      return addRClassInitializeWithDefaultValues(true, rClasses);
+    }
+
+    public AndroidTestResourceBuilder addRClassInitializeWithDefaultValues(
+        boolean addRClass, Class<?>... rClasses) {
       for (Class<?> rClass : rClasses) {
-        classesToRemap.add(rClass);
+        if (addRClass) {
+          classesToRemap.add(rClass);
+        }
         RClassType rClassType = RClassType.fromClass(rClass);
         for (Field declaredField : rClass.getDeclaredFields()) {
           String name = declaredField.getName();
@@ -364,6 +373,11 @@
       return this;
     }
 
+    public AndroidTestResourceBuilder addApiSpecificXml(String name, String content) {
+      apiSpecificXmlFiles.put(name, content);
+      return this;
+    }
+
     public AndroidTestResourceBuilder addKeepXmlFor(String... resourceReferences) {
       addRawXml("keep.xml", String.format(KEEP_XML, String.join(",", resourceReferences)));
       return this;
@@ -435,6 +449,13 @@
         }
       }
 
+      if (apiSpecificXmlFiles.size() > 0) {
+        File xmlFolder = temp.newFolder("res", "xml-v24");
+        for (Entry<String, String> entry : apiSpecificXmlFiles.entrySet()) {
+          FileUtils.writeTextFile(xmlFolder.toPath().resolve(entry.getKey()), entry.getValue());
+        }
+      }
+
       if (rawXmlFiles.size() > 0) {
         File rawXmlFolder = temp.newFolder("res", "raw");
         for (Entry<String, String> entry : rawXmlFiles.entrySet()) {