Add input and output consumers for android resources

This is in preperation of supporting resource shrinking in R8.

Currently we do a simple pass through of the resources.

Current API is marked as experimental in the api descriptions and compatability tests have not been added since we are not 100%
sure on the final api yet

Bug: 287399385
Change-Id: I8c45ebf0805a8ea1d3387f90106b9847dffba270
diff --git a/src/main/java/com/android/tools/r8/AndroidResourceConsumer.java b/src/main/java/com/android/tools/r8/AndroidResourceConsumer.java
new file mode 100644
index 0000000..634611f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/AndroidResourceConsumer.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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;
+
+@Keep
+public interface AndroidResourceConsumer {
+
+  void accept(AndroidResourceOutput androidResource, DiagnosticsHandler diagnosticsHandler);
+
+  default void finished(DiagnosticsHandler handler) {}
+}
diff --git a/src/main/java/com/android/tools/r8/AndroidResourceInput.java b/src/main/java/com/android/tools/r8/AndroidResourceInput.java
new file mode 100644
index 0000000..00f61c7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/AndroidResourceInput.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2023, 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;
+
+import java.io.InputStream;
+
+/**
+ * Base interface for android resources (resource table, manifest, res folder files)
+ *
+ * <p>Android resources are provided to R8 to allow for combined code and resource shrinking. This
+ * allows us to reason about resources, and transitively other resources and code throughout the
+ * entire compilation pipeline.
+ */
+@Keep
+public interface AndroidResourceInput extends Resource {
+  enum Kind {
+    // The AndroidManifest.xml file.
+    MANIFEST,
+    // The resource table, in either binary or proto format.
+    RESOURCE_TABLE,
+    // An xml file within the res folder.
+    XML_FILE,
+    // Any other binary file withing the res folder.
+    RES_FOLDER_FILE
+  }
+
+  // The path, within the app, of the resource.
+  ResourcePath getPath();
+
+  Kind getKind();
+
+  InputStream getByteStream() throws ResourceException;
+}
diff --git a/src/main/java/com/android/tools/r8/AndroidResourceOutput.java b/src/main/java/com/android/tools/r8/AndroidResourceOutput.java
new file mode 100644
index 0000000..6b204b5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/AndroidResourceOutput.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2023, 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;
+
+/**
+ * Base interface for android resources (resource table, manifest, res folder files)
+ *
+ * <p>Android resources are provided to R8 to allow for combined code and resource shrinking. This
+ * allows us to reason about resources, and transitively other resources and code throughout the
+ * entire compilation pipeline.
+ */
+@Keep
+public interface AndroidResourceOutput extends Resource {
+  // The path, within the app, of the resource.
+  ResourcePath getPath();
+
+  ByteDataView getByteDataView();
+}
diff --git a/src/main/java/com/android/tools/r8/AndroidResourceProvider.java b/src/main/java/com/android/tools/r8/AndroidResourceProvider.java
new file mode 100644
index 0000000..cb44ff0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/AndroidResourceProvider.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2023, 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;
+
+import java.util.Collection;
+
+/** Android resource provider. */
+@Keep
+public interface AndroidResourceProvider {
+
+  // Provide all android resources
+  Collection<AndroidResourceInput> getAndroidResources() throws ResourceException;
+
+  default void finished(DiagnosticsHandler handler) {}
+}
diff --git a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java
new file mode 100644
index 0000000..c601e80
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceConsumer.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2023, 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;
+
+import com.android.tools.r8.utils.ArchiveBuilder;
+import com.android.tools.r8.utils.OutputBuilder;
+import java.nio.file.Path;
+
+public class ArchiveProtoAndroidResourceConsumer implements AndroidResourceConsumer {
+  private final OutputBuilder outputBuilder;
+
+  public ArchiveProtoAndroidResourceConsumer(Path outputPath) {
+    this.outputBuilder = new ArchiveBuilder(outputPath);
+    this.outputBuilder.open();
+  }
+
+  @Override
+  public void accept(AndroidResourceOutput androidResource, DiagnosticsHandler diagnosticsHandler) {
+    outputBuilder.addFile(
+        androidResource.getPath().location(),
+        androidResource.getByteDataView(),
+        diagnosticsHandler);
+  }
+
+  @Override
+  public void finished(DiagnosticsHandler handler) {
+    outputBuilder.close(handler);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java
new file mode 100644
index 0000000..ecf9970
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ArchiveProtoAndroidResourceProvider.java
@@ -0,0 +1,123 @@
+// Copyright (c) 2023, 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;
+
+import com.android.tools.r8.AndroidResourceInput.Kind;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.io.ByteStreams;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Provides android resources from an archive.
+ *
+ * <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for
+ * resources in the descriptor set will then force the read of zip entry contents.
+ */
+@Keep
+public class ArchiveProtoAndroidResourceProvider implements AndroidResourceProvider {
+  private final Path archive;
+  private final Origin origin;
+
+  private static final String MANIFEST_NAME = "AndroidManifest.xml";
+  private static final String RESOURCE_TABLE = "resources.pb";
+  private static final String RES_FOLDER = "res/";
+  private static final String XML_SUFFIX = ".xml";
+
+  /**
+   * Creates an android resource provider from an archive.
+   *
+   * @param archive Zip archive to provide resources from.
+   * @param origin Origin of the archive.
+   */
+  public ArchiveProtoAndroidResourceProvider(Path archive, Origin origin) {
+    this.archive = archive;
+    this.origin = origin;
+  }
+
+  @Override
+  public Collection<AndroidResourceInput> getAndroidResources() throws ResourceException {
+    try (ZipFile zipFile = FileUtils.createZipFile(archive.toFile(), StandardCharsets.UTF_8)) {
+      List<AndroidResourceInput> resources = new ArrayList<>();
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
+        String name = entry.getName();
+        ByteAndroidResourceInput resource =
+            new ByteAndroidResourceInput(
+                name,
+                getKindFromName(name),
+                ByteStreams.toByteArray(zipFile.getInputStream(entry)),
+                new ArchiveEntryOrigin(name, origin));
+        resources.add(resource);
+      }
+      return resources;
+    } catch (IOException e) {
+      throw new ResourceException(origin, e);
+    }
+  }
+
+  private Kind getKindFromName(String name) {
+    if (name.equals(MANIFEST_NAME)) {
+      return Kind.MANIFEST;
+    }
+    if (name.equals(RESOURCE_TABLE)) {
+      return Kind.RESOURCE_TABLE;
+    }
+    if (!name.startsWith(RES_FOLDER)) {
+      throw new CompilationError("Unexpected non resource entry " + name, origin);
+    }
+    if (name.endsWith(XML_SUFFIX)) {
+      return Kind.XML_FILE;
+    }
+    return Kind.RES_FOLDER_FILE;
+  }
+
+  private static class ByteAndroidResourceInput implements AndroidResourceInput {
+
+    private final String name;
+    private final Kind kind;
+    private final byte[] bytes;
+    private final Origin origin;
+
+    public ByteAndroidResourceInput(String name, Kind kind, byte[] bytes, Origin origin) {
+      this.name = name;
+      this.kind = kind;
+      this.bytes = bytes;
+      this.origin = origin;
+    }
+
+    @Override
+    public ResourcePath getPath() {
+      return () -> name;
+    }
+
+    @Override
+    public Kind getKind() {
+      return kind;
+    }
+
+    @Override
+    public InputStream getByteStream() throws ResourceException {
+      return new ByteArrayInputStream(bytes);
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return origin;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 56d0a8c..0ce7a4e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -72,6 +72,7 @@
 import com.android.tools.r8.optimize.proto.ProtoNormalizer;
 import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemover;
 import com.android.tools.r8.origin.CommandLineOrigin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.repackaging.Repackaging;
@@ -102,6 +103,7 @@
 import com.android.tools.r8.synthesis.SyntheticFinalization;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SelfRetraceTest;
@@ -804,6 +806,12 @@
 
       new DesugaredLibraryKeepRuleGenerator(appView).runIfNecessary(timing);
 
+      if (options.androidResourceProvider != null && options.androidResourceConsumer != null) {
+        // Currently this is simply a pass through of all resources.
+        writeAndroidResources(
+            options.androidResourceProvider, options.androidResourceConsumer, appView.reporter());
+      }
+
       // Generate the resulting application resources.
       writeApplication(appView, inputApp, executorService);
 
@@ -822,6 +830,44 @@
     }
   }
 
+  private void writeAndroidResources(
+      AndroidResourceProvider androidResourceProvider,
+      AndroidResourceConsumer androidResourceConsumer,
+      DiagnosticsHandler diagnosticsHandler) {
+    try {
+      for (AndroidResourceInput androidResource : androidResourceProvider.getAndroidResources()) {
+        androidResourceConsumer.accept(
+            new AndroidResourceOutput() {
+              @Override
+              public ResourcePath getPath() {
+                return androidResource.getPath();
+              }
+
+              @Override
+              public ByteDataView getByteDataView() {
+                try {
+                  return ByteDataView.of(ByteStreams.toByteArray(androidResource.getByteStream()));
+                } catch (IOException | ResourceException e) {
+                  diagnosticsHandler.error(new ExceptionDiagnostic(e, androidResource.getOrigin()));
+                }
+                return null;
+              }
+
+              @Override
+              public Origin getOrigin() {
+                return androidResource.getOrigin();
+              }
+            },
+            diagnosticsHandler);
+      }
+    } catch (ResourceException e) {
+      throw new RuntimeException("Cannot write android resources", e);
+    } finally {
+      androidResourceConsumer.finished(diagnosticsHandler);
+      androidResourceProvider.finished(diagnosticsHandler);
+    }
+  }
+
   private static boolean allReferencesAssignedApiLevel(
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     if (!appView.options().apiModelingOptions().isCheckAllApiReferencesAreSet()
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 7ec72c1..4e89913 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -135,6 +135,8 @@
     private boolean enableMissingLibraryApiModeling = false;
     private boolean enableExperimentalKeepAnnotations = false;
     private SemanticVersion fakeCompilerVersion = null;
+    private AndroidResourceProvider androidResourceProvider = null;
+    private AndroidResourceConsumer androidResourceConsumer = null;
 
     private final ProguardConfigurationParserOptions.Builder parserOptionsBuilder =
         ProguardConfigurationParserOptions.builder().readEnvironment();
@@ -506,6 +508,28 @@
       return super.addStartupProfileProviders(startupProfileProviders);
     }
 
+    /**
+     * Exprimental API for supporting android resource shrinking.
+     *
+     * <p>Add an android resource provider, providing the resource table, manifest and res table
+     * entries.
+     */
+    public Builder setAndroidResourceProvider(AndroidResourceProvider provider) {
+      this.androidResourceProvider = provider;
+      return this;
+    }
+
+    /**
+     * Exprimental API for supporting android resource shrinking.
+     *
+     * <p>Add an android resource consumer, consuming the resource table, manifest and res table
+     * entries.
+     */
+    public Builder setAndroidResourceConsumer(AndroidResourceConsumer consumer) {
+      this.androidResourceConsumer = consumer;
+      return this;
+    }
+
     @Override
     void validate() {
       if (isPrintHelp()) {
@@ -659,7 +683,9 @@
               getArtProfilesForRewriting(),
               getStartupProfileProviders(),
               getClassConflictResolver(),
-              getCancelCompilationChecker());
+              getCancelCompilationChecker(),
+              androidResourceProvider,
+              androidResourceConsumer);
 
       if (inputDependencyGraphConsumer != null) {
         inputDependencyGraphConsumer.finished();
@@ -847,6 +873,8 @@
   private final FeatureSplitConfiguration featureSplitConfiguration;
   private final String synthesizedClassPrefix;
   private final boolean enableMissingLibraryApiModeling;
+  private final AndroidResourceProvider androidResourceProvider;
+  private final AndroidResourceConsumer androidResourceConsumer;
 
   /** Get a new {@link R8Command.Builder}. */
   public static Builder builder() {
@@ -941,7 +969,9 @@
       List<ArtProfileForRewriting> artProfilesForRewriting,
       List<StartupProfileProvider> startupProfileProviders,
       ClassConflictResolver classConflictResolver,
-      CancelCompilationChecker cancelCompilationChecker) {
+      CancelCompilationChecker cancelCompilationChecker,
+      AndroidResourceProvider androidResourceProvider,
+      AndroidResourceConsumer androidResourceConsumer) {
     super(
         inputApp,
         mode,
@@ -986,6 +1016,8 @@
     this.featureSplitConfiguration = featureSplitConfiguration;
     this.synthesizedClassPrefix = synthesizedClassPrefix;
     this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling;
+    this.androidResourceProvider = androidResourceProvider;
+    this.androidResourceConsumer = androidResourceConsumer;
   }
 
   private R8Command(boolean printHelp, boolean printVersion) {
@@ -1010,6 +1042,8 @@
     featureSplitConfiguration = null;
     synthesizedClassPrefix = null;
     enableMissingLibraryApiModeling = false;
+    androidResourceProvider = null;
+    androidResourceConsumer = null;
   }
 
   public DexItemFactory getDexItemFactory() {
@@ -1182,6 +1216,9 @@
 
     internal.cancelCompilationChecker = getCancelCompilationChecker();
 
+    internal.androidResourceProvider = androidResourceProvider;
+    internal.androidResourceConsumer = androidResourceConsumer;
+
     if (!DETERMINISTIC_DEBUGGING) {
       assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
       internal.threadCount = getThreadCount();
diff --git a/src/main/java/com/android/tools/r8/ResourcePath.java b/src/main/java/com/android/tools/r8/ResourcePath.java
new file mode 100644
index 0000000..eab4d7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ResourcePath.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2023, 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;
+
+@Keep
+public interface ResourcePath {
+
+  // The location within the apk, bundle or resource directory, e.g., res/xml/foo.xml
+  String location();
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 9afb2e9..09f5a8c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -6,6 +6,8 @@
 import static com.android.tools.r8.utils.AndroidApiLevel.B;
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
 
+import com.android.tools.r8.AndroidResourceConsumer;
+import com.android.tools.r8.AndroidResourceProvider;
 import com.android.tools.r8.CancelCompilationChecker;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -173,6 +175,8 @@
   // The state can only ever transition from false to true.
   private final AtomicBoolean cancelled = new AtomicBoolean(false);
   public CancelCompilationChecker cancelCompilationChecker = null;
+  public AndroidResourceProvider androidResourceProvider = null;
+  public AndroidResourceConsumer androidResourceConsumer = null;
 
   public boolean checkIfCancelled() {
     if (cancelCompilationChecker == null) {
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
new file mode 100644
index 0000000..b58587b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2023, 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 java.nio.charset.StandardCharsets;
+
+public class AndroidResourceTestingUtils {
+
+  // Taken from default empty android studio activity template
+  public static String TEST_MANIFEST =
+      "<?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\">\n"
+          + "\n"
+          + "    <application\n"
+          + "        android:allowBackup=\"true\"\n"
+          + "        android:dataExtractionRules=\"@xml/data_extraction_rules\"\n"
+          + "        android:fullBackupContent=\"@xml/backup_rules\"\n"
+          + "        android:icon=\"@mipmap/ic_launcher\"\n"
+          + "        android:label=\"@string/app_name\"\n"
+          + "        android:roundIcon=\"@mipmap/ic_launcher_round\"\n"
+          + "        android:supportsRtl=\"true\"\n"
+          + "        android:theme=\"@style/Theme.MyApplication\"\n"
+          + "        tools:targetApi=\"31\">\n"
+          + "        <activity\n"
+          + "            android:name=\".MainActivity\"\n"
+          + "            android:exported=\"true\"\n"
+          + "            android:label=\"@string/app_name\"\n"
+          + "            android:theme=\"@style/Theme.MyApplication.NoActionBar\">\n"
+          + "            <intent-filter>\n"
+          + "                <action android:name=\"android.intent.action.MAIN\" />\n"
+          + "\n"
+          + "                <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+          + "            </intent-filter>\n"
+          + "\n"
+          + "            <meta-data\n"
+          + "                android:name=\"android.app.lib_name\"\n"
+          + "                android:value=\"\" />\n"
+          + "        </activity>\n"
+          + "    </application>\n"
+          + "\n"
+          + "</manifest>";
+
+  // TODO(287399385): Add testing utils for generating/consuming resource tables.
+  public static byte[] TEST_RESOURCE_TABLE = "RESOURCE_TABLE".getBytes(StandardCharsets.UTF_8);
+
+  // The below byte arrays are lifted from the resource shrinkers DummyContent
+
+  // A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY
+  public static final byte[] TINY_PNG =
+      new byte[] {
+        (byte) -119, (byte) 80, (byte) 78, (byte) 71, (byte) 13, (byte) 10,
+        (byte) 26, (byte) 10, (byte) 0, (byte) 0, (byte) 0, (byte) 13,
+        (byte) 73, (byte) 72, (byte) 68, (byte) 82, (byte) 0, (byte) 0,
+        (byte) 0, (byte) 1, (byte) 0, (byte) 0, (byte) 0, (byte) 1,
+        (byte) 8, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 58,
+        (byte) 126, (byte) -101, (byte) 85, (byte) 0, (byte) 0, (byte) 0,
+        (byte) 10, (byte) 73, (byte) 68, (byte) 65, (byte) 84, (byte) 120,
+        (byte) -38, (byte) 99, (byte) 96, (byte) 0, (byte) 0, (byte) 0,
+        (byte) 2, (byte) 0, (byte) 1, (byte) -27, (byte) 39, (byte) -34,
+        (byte) -4, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 73,
+        (byte) 69, (byte) 78, (byte) 68, (byte) -82, (byte) 66, (byte) 96,
+        (byte) -126
+      };
+
+  // The XML document <x/> as a proto packed with AAPT2
+  public static final byte[] TINY_PROTO_XML =
+      new byte[] {0xa, 0x3, 0x1a, 0x1, 0x78, 0x1a, 0x2, 0x8, 0x1};
+}
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java
new file mode 100644
index 0000000..f0bb82a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidResourcesPassthroughTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2023, 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 org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ArchiveProtoAndroidResourceConsumer;
+import com.android.tools.r8.ArchiveProtoAndroidResourceProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Arrays;
+import org.junit.Test;
+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 AndroidResourcesPassthroughTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    String manifestPath = "AndroidManifest.xml";
+    String resourcePath = "resources.pb";
+    String pngPath = "res/drawable/foo.png";
+    String xmlPath = "res/xml/bar.xml";
+    Path resources =
+        ZipBuilder.builder(temp.newFile("resources.zip").toPath())
+            .addText(manifestPath, AndroidResourceTestingUtils.TEST_MANIFEST)
+            .addBytes(resourcePath, AndroidResourceTestingUtils.TEST_RESOURCE_TABLE)
+            .addBytes(pngPath, AndroidResourceTestingUtils.TINY_PNG)
+            .addBytes(xmlPath, AndroidResourceTestingUtils.TINY_PROTO_XML)
+            .build();
+    Path output = temp.newFile("resources_out.zip").toPath();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters)
+        .addOptionsModification(
+            o -> {
+              o.androidResourceProvider =
+                  new ArchiveProtoAndroidResourceProvider(resources, new PathOrigin(resources));
+              o.androidResourceConsumer = new ArchiveProtoAndroidResourceConsumer(output);
+            })
+        .addKeepMainRule(FooBar.class)
+        .run(parameters.getRuntime(), FooBar.class)
+        .assertSuccessWithOutputLines("Hello World");
+    assertTrue(
+        Arrays.equals(
+            ZipUtils.readSingleEntry(output, manifestPath),
+            AndroidResourceTestingUtils.TEST_MANIFEST.getBytes(StandardCharsets.UTF_8)));
+    assertTrue(
+        Arrays.equals(
+            ZipUtils.readSingleEntry(output, resourcePath),
+            AndroidResourceTestingUtils.TEST_RESOURCE_TABLE));
+    assertTrue(
+        Arrays.equals(
+            ZipUtils.readSingleEntry(output, pngPath), AndroidResourceTestingUtils.TINY_PNG));
+    assertTrue(
+        Arrays.equals(
+            ZipUtils.readSingleEntry(output, xmlPath), AndroidResourceTestingUtils.TINY_PROTO_XML));
+  }
+
+  public static class FooBar {
+
+    public static void main(String[] args) {
+      System.out.println("Hello World");
+    }
+  }
+}