Trace AndroidManifest.xml in the enqueuer

Bug: 326008763
Bug: 287398085
Change-Id: Id853f29929a6750381a2c3513e5567165a3b9dd2
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 0dfda11..e52bb2e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3771,6 +3771,7 @@
     enqueueAllIfNotShrinking();
     timing.end();
     timing.begin("Trace");
+    traceManifests(timing);
     trace(executorService, timing);
     timing.end();
     options.reporter.failIfPendingErrors();
@@ -3804,6 +3805,14 @@
     return result;
   }
 
+  private void traceManifests(Timing timing) {
+    if (options.isOptimizedResourceShrinking()) {
+      timing.begin("Trace AndroidManifest.xml files");
+      appView.getResourceShrinkerState().traceManifests();
+      timing.end();
+    }
+  }
+
   private void includeMinimumKeepInfo(RootSetBase rootSet) {
     rootSet
         .getDependentMinimumKeepInfo()
diff --git a/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java b/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
index 2d4e961..41ad64f 100644
--- a/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
@@ -55,7 +55,7 @@
     for (AndroidResourceInput androidResource : androidResources) {
       switch (androidResource.getKind()) {
         case MANIFEST:
-          state.setManifestProvider(
+          state.addManifestProvider(
               () -> wrapThrowingInputStreamResource(appView, androidResource));
           break;
         case RESOURCE_TABLE:
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 d8c98c7..11bb767 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -38,6 +38,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -54,7 +55,7 @@
   private final R8ResourceShrinkerModel r8ResourceShrinkerModel;
   private final Map<String, Supplier<InputStream>> xmlFileProviders = new HashMap<>();
 
-  private Supplier<InputStream> manifestProvider;
+  private final List<Supplier<InputStream>> manifestProviders = new ArrayList<>();
   private final Map<String, Supplier<InputStream>> resfileProviders = new HashMap<>();
   private final Map<ResourceTable, FeatureSplit> resourceTables = new HashMap<>();
   private ClassReferenceCallback enqueuerCallback;
@@ -82,7 +83,7 @@
       return;
     }
     ResourceUsageModel.markReachable(resource);
-    traceXml(id);
+    traceXmlForResourceId(id);
     if (resource.references != null) {
       for (Resource reference : resource.references) {
         if (!reference.isReachable()) {
@@ -92,6 +93,12 @@
     }
   }
 
+  public void traceManifests() {
+    for (Supplier<InputStream> manifestProvider : manifestProviders) {
+      traceXml("AndroidManifest.xml", manifestProvider.get());
+    }
+  }
+
   public void setEnqueuerCallback(ClassReferenceCallback enqueuerCallback) {
     assert this.enqueuerCallback == null;
     this.enqueuerCallback = enqueuerCallback;
@@ -111,8 +118,8 @@
     return packageNames;
   }
 
-  public void setManifestProvider(Supplier<InputStream> manifestProvider) {
-    this.manifestProvider = manifestProvider;
+  public void addManifestProvider(Supplier<InputStream> manifestProvider) {
+    this.manifestProviders.add(manifestProvider);
   }
 
   public void addXmlFileProvider(Supplier<InputStream> inputStreamSupplier, String location) {
@@ -185,19 +192,24 @@
     return resEntriesToKeep.build();
   }
 
-  private void traceXml(int id) {
+  private void traceXmlForResourceId(int id) {
     String xmlFile = getResourceIdToXmlFiles().get(id);
     if (xmlFile != null) {
       InputStream inputStream = xmlFileProviders.get(xmlFile).get();
-      try {
-        XmlNode xmlNode = XmlNode.parseFrom(inputStream);
-        visitNode(xmlNode, xmlFile);
-      } catch (IOException e) {
-        throw new RuntimeException(e);
-      }
+      traceXml(xmlFile, inputStream);
     }
   }
 
+  private void traceXml(String xmlFile, InputStream inputStream) {
+    try {
+      XmlNode xmlNode = XmlNode.parseFrom(inputStream);
+      visitNode(xmlNode, xmlFile);
+    } catch (IOException e) {
+      errorHandler.apply(e);
+    }
+  }
+
+
   private void tryEnqueuerOnString(String possibleClass, String xmlName) {
     // There are a lot of xml tags and attributes that are evaluated over and over, if it is
     // not a class, ignore it.
@@ -264,11 +276,10 @@
   // Temporary to support updating the reachable entries from the manifest, we need to instead
   // trace these in the enqueuer.
   public void updateModelWithManifestReferences() throws IOException {
-    if (manifestProvider == null) {
-      return;
+    for (Supplier<InputStream> manifestProvider : manifestProviders) {
+      ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(
+          XmlNode.parseFrom(manifestProvider.get()), r8ResourceShrinkerModel);
     }
-    ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(
-        XmlNode.parseFrom(manifestProvider.get()), r8ResourceShrinkerModel);
   }
 
   public void updateModelWithKeepXmlReferences() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidManifestWithCodeReferences.java b/src/test/java/com/android/tools/r8/androidresources/AndroidManifestWithCodeReferences.java
new file mode 100644
index 0000000..03f1663
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidManifestWithCodeReferences.java
@@ -0,0 +1,107 @@
+// 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.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+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 com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 AndroidManifestWithCodeReferences extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+  }
+
+  public static String MANIFEST_WITH_CLASS_REFERENCE =
+      "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+          + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+          + "    xmlns:tools=\"http://schemas.android.com/tools\""
+          + "    package=\"com.android.tools.r8\""
+          + ">\n"
+          + "    <application\n"
+          + "        android:label=\"@string/app_name\">\n"
+          + "      <activity\n"
+          + "            android:name=\""
+          + Bar.class.getTypeName()
+          + "\"\n"
+          + "            android:exported=\"true\">\n"
+          + "            <intent-filter>\n"
+          + "                <action android:name=\"android.intent.action.MAIN\" />\n"
+          + "\n"
+          + "                <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+          + "            </intent-filter>\n"
+          + "        </activity>\n"
+          + "    </application>\n"
+          + "</manifest>";
+
+  public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+    return new AndroidTestResourceBuilder()
+        .withManifest(MANIFEST_WITH_CLASS_REFERENCE)
+        .addStringValue("app_name", "The one and only.")
+        .build(temp);
+  }
+
+  @Test
+  public void testManifestReferences() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addProgramClasses(TestClass.class, Bar.class)
+        .addAndroidResources(getTestResources(temp))
+        .addKeepMainRule(TestClass.class)
+        .enableOptimizedShrinking()
+        .compile()
+        .inspectShrunkenResources(
+            resourceTableInspector -> {
+              resourceTableInspector.assertContainsResourceWithName("string", "app_name");
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(
+            codeInspector -> {
+              ClassSubject barClass = codeInspector.clazz(Bar.class);
+              assertThat(barClass, isPresentAndNotRenamed());
+              // We should have two and only two methods, the two constructors.
+              assertEquals(barClass.allMethods(MethodSubject::isInstanceInitializer).size(), 2);
+              assertEquals(barClass.allMethods().size(), 2);
+            })
+        .assertSuccess();
+  }
+
+  public static class TestClass {
+    public static void main(String[] args) {}
+  }
+
+  // Only referenced from Manifest file
+  public static class Bar {
+    public Bar() {
+      System.out.println("init");
+    }
+
+    public Bar(String x) {
+      System.out.println("init with string");
+    }
+
+    public void bar() {
+      System.out.println("never kept");
+    }
+  }
+}