Merge "Update nullability tests for local type analysis."
diff --git a/.gitignore b/.gitignore
index 0661690..b66a429 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,8 @@
 tools/*/art-7.0.0.tar.gz
 tools/*/dalvik
 tools/*/dalvik.tar.gz
+tools/*/dalvik-4.0.4
+tools/*/dalvik-4.0.4.tar.gz
 tools/*/dx
 tools/*/dx.tar.gz
 third_party/android_jar/lib
diff --git a/build.gradle b/build.gradle
index 418474a..2d98276 100644
--- a/build.gradle
+++ b/build.gradle
@@ -271,6 +271,8 @@
                 "android_jar/lib-v15",
                 "android_jar/lib-v19",
                 "android_jar/lib-v21",
+                "android_jar/lib-v22",
+                "android_jar/lib-v23",
                 "android_jar/lib-v24",
                 "android_jar/lib-v25",
                 "android_jar/lib-v26",
@@ -292,6 +294,7 @@
                 "linux/art-6.0.1",
                 "linux/art-7.0.0",
                 "linux/dalvik",
+                "linux/dalvik-4.0.4",
                 "${osString}/dx",
         ]
 ]
@@ -709,6 +712,18 @@
     destinationDir file('tests')
 }
 
+task buildD8ApiUsageSample(type: Jar) {
+    from sourceSets.apiUsageSample.output
+    baseName 'd8_api_usage_sample'
+    destinationDir file('tests')
+}
+
+task buildR8ApiUsageSample(type: Jar) {
+    from sourceSets.apiUsageSample.output
+    baseName 'r8_api_usage_sample'
+    destinationDir file('tests')
+}
+
 task buildDebugInfoExamplesDex {
     def examplesDir = file("src/test/java")
     def hostJar = "debuginfo_examples.jar"
@@ -1667,22 +1682,58 @@
 }
 
 task javadocD8(type: Javadoc) {
+  title "D8 API"
   classpath = sourceSets.main.compileClasspath
   source = sourceSets.main.allJava
   include '**/com/android/tools/r8/ArchiveClassFileProvider.java'
+  include '**/com/android/tools/r8/ArchiveProgramResourceProvider.java'
   include '**/com/android/tools/r8/BaseCommand.java'
-  include '**/com/android/tools/r8/BaseOutput.java'
+  include '**/com/android/tools/r8/BaseCompilerCommand.java'
   include '**/com/android/tools/r8/ClassFileResourceProvider.java'
   include '**/com/android/tools/r8/CompilationFailedException.java'
   include '**/com/android/tools/r8/CompilationMode.java'
   include '**/com/android/tools/r8/D8.java'
   include '**/com/android/tools/r8/D8Command.java'
-  include '**/com/android/tools/r8/D8Output.java'
+  include '**/com/android/tools/r8/DexIndexedConsumer.java'
+  include '**/com/android/tools/r8/DexFilePerClassFileConsumer.java'
   include '**/com/android/tools/r8/Diagnostic.java'
   include '**/com/android/tools/r8/DiagnosticsHandler.java'
-  include '**/com/android/tools/r8/Location.java'
+  include '**/com/android/tools/r8/ProgramConsumer.java'
+  include '**/com/android/tools/r8/ProgramResource.java'
+  include '**/com/android/tools/r8/ProgramResourceProvider.java'
   include '**/com/android/tools/r8/Resource.java'
-  include '**/com/android/tools/r8/TextRangeLocation.java'
+  include '**/com/android/tools/r8/ResourceException.java'
+  include '**/com/android/tools/r8/StringConsumer.java'
+  include '**/com/android/tools/r8/StringResource.java'
+  include '**/com/android/tools/r8/Version.java'
+  include '**/com/android/tools/r8/origin/*.java'
+}
+
+task javadocR8(type: Javadoc) {
+  title "R8 API"
+  classpath = sourceSets.main.compileClasspath
+  source = sourceSets.main.allJava
+  include '**/com/android/tools/r8/ArchiveClassFileProvider.java'
+  include '**/com/android/tools/r8/ArchiveProgramResourceProvider.java'
+  include '**/com/android/tools/r8/BaseCommand.java'
+  include '**/com/android/tools/r8/BaseCompilerCommand.java'
+  include '**/com/android/tools/r8/ClassFileConsumer.java'
+  include '**/com/android/tools/r8/ClassFileResourceProvider.java'
+  include '**/com/android/tools/r8/CompilationFailedException.java'
+  include '**/com/android/tools/r8/CompilationMode.java'
+  include '**/com/android/tools/r8/R8.java'
+  include '**/com/android/tools/r8/R8Command.java'
+  include '**/com/android/tools/r8/DexIndexedConsumer.java'
+  include '**/com/android/tools/r8/Diagnostic.java'
+  include '**/com/android/tools/r8/DiagnosticsHandler.java'
+  include '**/com/android/tools/r8/ProgramConsumer.java'
+  include '**/com/android/tools/r8/ProgramResource.java'
+  include '**/com/android/tools/r8/ProgramResourceProvider.java'
+  include '**/com/android/tools/r8/Resource.java'
+  include '**/com/android/tools/r8/ResourceException.java'
+  include '**/com/android/tools/r8/StringConsumer.java'
+  include '**/com/android/tools/r8/StringResource.java'
+  include '**/com/android/tools/r8/Version.java'
   include '**/com/android/tools/r8/origin/*.java'
 }
 
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 10c92dc..ffd51c0 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -23,9 +23,9 @@
 
 /**
  * Base class for commands and command builders for applications/tools which take an Android
- * application (and a main-dex list) as input.
+ * application sources (and optional main-dex list) as input.
  */
-abstract class BaseCommand {
+public abstract class BaseCommand {
 
   private final boolean printHelp;
   private final boolean printVersion;
@@ -175,12 +175,6 @@
       return self();
     }
 
-    /** Add dex program-data. */
-    public B addDexProgramData(byte[] data, Origin origin) {
-      guard(() -> app.addDexProgramData(data, origin));
-      return self();
-    }
-
     /**
      * Add main-dex list files.
      *
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 4ed89d4..f42353a 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -12,15 +12,18 @@
 import com.android.tools.r8.utils.OutputMode;
 import com.android.tools.r8.utils.Reporter;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Base class for commands and command builders for compiler applications/tools which besides an
- * Android application (and a main-dex list) also takes compilation output, compilation mode and
- * min API level as input.
+ * Android application (and optional main-dex list) also configure compilation output, compilation
+ * mode and min API level.
  */
-abstract class BaseCompilerCommand extends BaseCommand {
+public abstract class BaseCompilerCommand extends BaseCommand {
 
   // TODO(b/70656566): Remove this once the deprecated API is removed.
+  @Deprecated
   protected static class OutputOptions {
     final Path path;
     final OutputMode mode;
@@ -297,8 +300,31 @@
 
     @Override
     protected void validate() {
-      assert mode != null;
+      if (mode == null) {
+        reporter.error("Expected valid compilation mode, was null");
+      }
       FileUtils.validateOutputFile(outputPath, reporter);
+      List<Class> programConsumerClasses = new ArrayList<>(3);
+      if (programConsumer instanceof DexIndexedConsumer) {
+        programConsumerClasses.add(DexIndexedConsumer.class);
+      }
+      if (programConsumer instanceof DexFilePerClassFileConsumer) {
+        programConsumerClasses.add(DexFilePerClassFileConsumer.class);
+      }
+      if (programConsumer instanceof ClassFileConsumer) {
+        programConsumerClasses.add(ClassFileConsumer.class);
+      }
+      if (programConsumerClasses.size() > 1) {
+        StringBuilder builder = new StringBuilder()
+            .append("Invalid program consumer.")
+            .append(" A program consumer can implement at most one consumer type but ")
+            .append(programConsumer.getClass().getName())
+            .append(" implements types:");
+        for (Class clazz : programConsumerClasses) {
+          builder.append(" ").append(clazz.getName());
+        }
+        reporter.error(builder.toString());
+      }
       super.validate();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/BaseOutput.java b/src/main/java/com/android/tools/r8/BaseOutput.java
index 222ea6d..3ca5665 100644
--- a/src/main/java/com/android/tools/r8/BaseOutput.java
+++ b/src/main/java/com/android/tools/r8/BaseOutput.java
@@ -45,7 +45,7 @@
    */
   public List<Resource> getDexResources() {
     try {
-      return ImmutableList.copyOf(app.getDexProgramResources());
+      return ImmutableList.copyOf(app.getDexProgramResourcesForTesting());
     } catch (IOException e) {
       throw new InternalCompilerError("Unexpected resource error", e);
     }
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index fca359f..f10bd31 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -44,7 +44,7 @@
  * <pre>
  *   D8.run(D8Command.builder()
  *       .addProgramFiles(inputPathA, inputPathB)
- *       .setOutputPath(outputPath)
+ *       .setOutput(outputPath, OutputMode.DexIndexed)
  *       .build());
  * </pre>
  *
@@ -80,10 +80,7 @@
   }
 
   /**
-   * Main API entry for the D8 dexer.
-   *
-   * <p>The D8 dexer API is intentionally limited and should "do the right thing" given a set of
-   * inputs. If the API does not suffice please contact the R8 team.
+   * Main API entry for the D8 dexer with a externally supplied executor service.
    *
    * @param command D8 command.
    * @param executor executor service from which to get threads for multi-threaded processing.
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index fece59d..39462f9 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -28,6 +28,7 @@
  *   D8Command command = D8Command.builder()
  *     .addProgramFiles(path1, path2)
  *     .setMode(CompilationMode.RELEASE)
+ *     .setOutput(Paths.get("output.zip", OutputMode.DexIndexed))
  *     .build();
  * </pre>
  */
@@ -52,7 +53,13 @@
     private Builder(AndroidApp app) {
       super(app);
       setMode(CompilationMode.DEBUG);
-   }
+    }
+
+    /** Add dex program-data. */
+    public Builder addDexProgramData(byte[] data, Origin origin) {
+      guard(() -> getAppBuilder().addDexProgramData(data, origin));
+      return self();
+    }
 
     /** Add classpath file resources. */
     public Builder addClasspathFiles(Path... files) {
diff --git a/src/main/java/com/android/tools/r8/DexRoundTrip.java b/src/main/java/com/android/tools/r8/DexRoundTrip.java
index 01af3b8..c99e939 100644
--- a/src/main/java/com/android/tools/r8/DexRoundTrip.java
+++ b/src/main/java/com/android/tools/r8/DexRoundTrip.java
@@ -42,7 +42,8 @@
     return consumer.build();
   }
 
-  public static void main(String[] args) throws CompilationFailedException, IOException {
+  public static void main(String[] args)
+      throws CompilationFailedException, IOException, ResourceException {
     List<ProgramResource> resources = new ArrayList<>(args.length);
     for (String arg : args) {
       Path file = Paths.get(arg);
@@ -53,6 +54,6 @@
       resources.add(ProgramResource.fromFile(Kind.DEX, file));
     }
     AndroidApp result = process(resources);
-    process(result.getDexProgramResources());
+    process(result.computeAllProgramResources());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 1d9f129..72ac766 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.DexFileReader;
 import com.android.tools.r8.dex.Segment;
 import com.android.tools.r8.origin.CommandLineOrigin;
@@ -85,7 +86,7 @@
   }
 
   public static void main(String[] args)
-      throws IOException, CompilationFailedException {
+      throws IOException, CompilationFailedException, ResourceException {
     Command.Builder builder = Command.parse(args);
     Command command = builder.build();
     if (command.isPrintHelp()) {
@@ -95,12 +96,14 @@
     AndroidApp app = command.getInputApp();
     Map<String, Integer> result = new HashMap<>();
     try (Closer closer = Closer.create()) {
-      for (Resource resource : app.getDexProgramResources()) {
-        for (Segment segment :
-            DexFileReader.parseMapFrom(
-                closer.register(resource.getStream()), resource.getOrigin())) {
-          int value = result.computeIfAbsent(segment.typeName(), (key) -> 0);
-          result.put(segment.typeName(), value + segment.size());
+      for (ProgramResource resource : app.computeAllProgramResources()) {
+        if (resource.getKind() == Kind.DEX) {
+          for (Segment segment :
+              DexFileReader.parseMapFrom(
+                  closer.register(resource.getByteStream()), resource.getOrigin())) {
+            int value = result.computeIfAbsent(segment.typeName(), (key) -> 0);
+            result.put(segment.typeName(), value + segment.size());
+          }
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 3a7ab99..272d2e5 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.VDexFile;
@@ -51,9 +52,11 @@
     AndroidApp.Builder appBuilder = AndroidApp.builder();
     addDexResources(appBuilder, file);
     int size = 0;
-    for (ProgramResource resource : appBuilder.build().getDexProgramResources()) {
-      try (InputStream input = resource.getByteStream()) {
-        size += ByteStreams.toByteArray(input).length;
+    for (ProgramResource resource : appBuilder.build().computeAllProgramResources()) {
+      if (resource.getKind() == Kind.DEX) {
+        try (InputStream input = resource.getByteStream()) {
+          size += ByteStreams.toByteArray(input).length;
+        }
       }
     }
     return size;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 304dde3..032c07a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -46,7 +46,6 @@
 import com.android.tools.r8.shaking.protolite.ProtoLiteExtension;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -72,6 +71,35 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
+/**
+ * The R8 compiler.
+ *
+ * <p>R8 performs whole-program optimizing compilation of Java bytecode. It supports compilation of
+ * Java bytecode to Java bytecode or DEX bytecode. R8 supports tree-shaking the program to remove
+ * unneeded code and it supports minification of the program names to reduce the size of the
+ * resulting program.
+ *
+ * <p>The R8 API is intentionally limited and should "do the right thing" given a command. If this
+ * API does not suffice please contact the D8/R8 team.
+ *
+ * <p>R8 supports some configuration using configuration files mostly compatible with the format of
+ * the <a href="https://www.guardsquare.com/en/proguard">ProGuard</a> optimizer.
+ *
+ * <p>The compiler is invoked by calling {@link #run(R8Command) R8.run} with an appropriate {@link
+ * R8Command}. For example:
+ *
+ * <pre>
+ *   R8.run(R8Command.builder()
+ *       .addProgramFiles(inputPathA, inputPathB)
+ *       .addLibraryFiles(androidJar)
+ *       .setOutput(outputPath, OutputMode.DexIndexed)
+ *       .build());
+ * </pre>
+ *
+ * The above reads the input files denoted by {@code inputPathA} and {@code inputPathB}, compiles
+ * them to DEX bytecode, using {@code androidJar} as the reference of the system runtime library,
+ * and then writes the result to the directory or zip archive specified by {@code outputPath}.
+ */
 public class R8 {
 
   private final Timing timing = new Timing("R8");
@@ -82,6 +110,49 @@
     options.itemFactory.resetSortedIndices();
   }
 
+  /**
+   * Main API entry for the R8 compiler.
+   *
+   * <p>The R8 API is intentionally limited and should "do the right thing" given a command. If this
+   * API does not suffice please contact the R8 team.
+   *
+   * @param command R8 command.
+   */
+  public static void run(R8Command command) throws CompilationFailedException {
+    AndroidApp app = command.getInputApp();
+    InternalOptions options = command.getInternalOptions();
+    ExecutorService executor = ThreadUtils.getExecutorService(options);
+    ExceptionUtils.withR8CompilationHandler(
+        command.getReporter(),
+        () -> {
+          try {
+            run(app, options, executor);
+          } finally {
+            executor.shutdown();
+          }
+        });
+  }
+
+  /**
+   * Main API entry for the R8 compiler.
+   *
+   * <p>The R8 API is intentionally limited and should "do the right thing" given a command. If this
+   * API does not suffice please contact the R8 team.
+   *
+   * @param command R8 command.
+   * @param executor executor service from which to get threads for multi-threaded processing.
+   */
+  public static void run(R8Command command, ExecutorService executor)
+      throws CompilationFailedException {
+    AndroidApp app = command.getInputApp();
+    InternalOptions options = command.getInternalOptions();
+    ExceptionUtils.withR8CompilationHandler(
+        command.getReporter(),
+        () -> {
+          run(app, options, executor);
+        });
+  }
+
   // Compute the marker to be placed in the main dex file.
   private static Marker getMarker(InternalOptions options) {
     if (options.hasMarker()) {
@@ -96,7 +167,7 @@
     return marker;
   }
 
-  public static void writeApplication(
+  static void writeApplication(
       ExecutorService executorService,
       DexApplication application,
       String deadCode,
@@ -424,71 +495,6 @@
     }
   }
 
-  /**
-   * Main API entry for the R8 compiler.
-   *
-   * <p>The R8 API is intentionally limited and should "do the right thing" given a command. If this
-   * API does not suffice please contact the R8 team.
-   *
-   * @param command R8 command.
-   */
-  public static void run(R8Command command) throws CompilationFailedException {
-    AndroidApp app = command.getInputApp();
-    InternalOptions options = command.getInternalOptions();
-    ExecutorService executor = ThreadUtils.getExecutorService(options);
-    ExceptionUtils.withR8CompilationHandler(
-        command.getReporter(),
-        () -> {
-          try {
-            run(app, options, executor);
-          } finally {
-            executor.shutdown();
-          }
-        });
-  }
-
-  /**
-   * Main API entry for the R8 compiler.
-   *
-   * <p>The R8 API is intentionally limited and should "do the right thing" given a command. If this
-   * API does not suffice please contact the R8 team.
-   *
-   * @param command R8 command.
-   * @param executor executor service from which to get threads for multi-threaded processing.
-   */
-  public static void run(R8Command command, ExecutorService executor)
-      throws CompilationFailedException {
-    AndroidApp app = command.getInputApp();
-    InternalOptions options = command.getInternalOptions();
-    ExceptionUtils.withR8CompilationHandler(
-        command.getReporter(),
-        () -> {
-          run(app, options, executor);
-        });
-  }
-
-  /** TODO(sgjesse): Get rid of this. */
-  public static AndroidApp runInternal(R8Command command) throws IOException, CompilationException {
-    InternalOptions options = command.getInternalOptions();
-    ExecutorService executorService = ThreadUtils.getExecutorService(options);
-    try {
-      return runInternal(command, executorService);
-    } finally {
-      executorService.shutdown();
-    }
-  }
-
-  /**
-   * TODO(sgjesse): Get rid of this.
-   */
-  public static AndroidApp runInternal(R8Command command, ExecutorService executor)
-      throws IOException, CompilationException {
-    InternalOptions options = command.getInternalOptions();
-    AndroidAppConsumers compatConsumers = new AndroidAppConsumers(options);
-    run(command.getInputApp(), options, executor);
-    return compatConsumers.build();
-  }
-
   private static void run(String[] args) throws CompilationFailedException {
     R8Command command = R8Command.parse(args, CommandLineOrigin.INSTANCE).build();
     if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index cae894d..f382506 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
@@ -27,6 +28,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Consumer;
@@ -112,10 +114,8 @@
       return self();
     }
 
-    /**
-     * Add proguard configuration file resources for automatic main dex list calculation.
-     */
-    public Builder addMainDexRulesFiles(List<Path> paths) {
+    /** Add proguard configuration file resources for automatic main dex list calculation. */
+    public Builder addMainDexRulesFiles(Collection<Path> paths) {
       guard(() -> {
         for (Path path : paths) {
           mainDexRules.add(new ProguardConfigurationSourceFile(path));
@@ -192,20 +192,6 @@
     }
 
     /**
-     * Add and/or chain proguard configuration consumer(s) for testing.
-     */
-    public Builder addProguardConfigurationConsumer(Consumer<ProguardConfiguration.Builder> c) {
-      Consumer<ProguardConfiguration.Builder> oldConsumer = proguardConfigurationConsumer;
-      proguardConfigurationConsumer = builder -> {
-        if (oldConsumer != null) {
-          oldConsumer.accept(builder);
-        }
-        c.accept(builder);
-      };
-      return self();
-    }
-
-    /**
      * Set an output destination to which proguard-map content should be written.
      *
      * <p>This is a short-hand for setting a {@link StringConsumer.FileConsumer} using {@link
@@ -244,6 +230,12 @@
     }
 
     @Override
+    public Builder addProgramResourceProvider(ProgramResourceProvider programProvider) {
+      return super.addProgramResourceProvider(
+          new EnsureNonDexProgramResourceProvider(programProvider));
+    }
+
+    @Override
     protected void validate() {
       // TODO(b/70656566): Move up super once the deprecated API is removed.
       if (getProgramConsumer() == null) {
@@ -259,6 +251,12 @@
         reporter.error(
             "Option --main-dex-list-output require --main-dex-rules and/or --main-dex-list");
       }
+      for (Path file : programFiles) {
+        if (FileUtils.isDexFile(file)) {
+          reporter.error(new StringDiagnostic(
+              "R8 does not support compiling DEX inputs", new PathOrigin(file)));
+        }
+      }
       super.validate();
     }
 
@@ -337,6 +335,40 @@
 
       return command;
     }
+
+    // Internal for-testing method to add post-processors of the proguard configuration.
+    void addProguardConfigurationConsumerForTesting(Consumer<ProguardConfiguration.Builder> c) {
+      Consumer<ProguardConfiguration.Builder> oldConsumer = proguardConfigurationConsumer;
+      proguardConfigurationConsumer =
+          builder -> {
+            if (oldConsumer != null) {
+              oldConsumer.accept(builder);
+            }
+            c.accept(builder);
+          };
+    }
+  }
+
+  // Wrapper class to ensure that R8 does not allow DEX as program inputs.
+  private static class EnsureNonDexProgramResourceProvider implements ProgramResourceProvider {
+
+    final ProgramResourceProvider provider;
+
+    public EnsureNonDexProgramResourceProvider(ProgramResourceProvider provider) {
+      this.provider = provider;
+    }
+
+    @Override
+    public Collection<ProgramResource> getProgramResources() throws ResourceException {
+      Collection<ProgramResource> resources = provider.getProgramResources();
+      for (ProgramResource resource : resources) {
+        if (resource.getKind() == Kind.DEX) {
+          throw new ResourceException(resource.getOrigin(),
+              "R8 does not support compiling DEX inputs");
+        }
+      }
+      return resources;
+    }
   }
 
   // Internal state to verify parsing properties not enforced by the builder.
diff --git a/src/main/java/com/android/tools/r8/Utf8Consumer.java b/src/main/java/com/android/tools/r8/Utf8Consumer.java
deleted file mode 100644
index 4da33d5..0000000
--- a/src/main/java/com/android/tools/r8/Utf8Consumer.java
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (c) 2017, 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.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Path;
-
-/** Interface for receiving UTF8 encoded text data. */
-public interface Utf8Consumer {
-
-  /**
-   * Callback to receive UTF8 encoded text data.
-   *
-   * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
-   * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
-   * then the compiler guaranties to exit with an error.
-   *
-   * @param data UTF-8 encoded text data.
-   * @param handler Diagnostics handler for reporting.
-   */
-  void accept(byte[] data, DiagnosticsHandler handler);
-
-  static EmptyConsumer emptyConsumer() {
-    return EmptyConsumer.EMPTY_CONSUMER;
-  }
-
-  /** Empty consumer to request the production of the resource but ignore its value. */
-  class EmptyConsumer implements Utf8Consumer {
-
-    private static EmptyConsumer EMPTY_CONSUMER = new EmptyConsumer();
-
-    @Override
-    public void accept(byte[] data, DiagnosticsHandler handler) {
-      // Ignore content.
-    }
-  }
-
-  /** Forwarding consumer to delegate to an optional existing consumer. */
-  class ForwardingConsumer implements Utf8Consumer {
-
-    private final Utf8Consumer consumer;
-
-    /** @param consumer Consumer to forward to, if null, nothing will be forwarded. */
-    public ForwardingConsumer(Utf8Consumer consumer) {
-      this.consumer = consumer;
-    }
-
-    @Override
-    public void accept(byte[] data, DiagnosticsHandler handler) {
-      if (consumer != null) {
-        consumer.accept(data, handler);
-      }
-    }
-  }
-
-  /** File consumer to write contents to a file-system file. */
-  class FileConsumer extends ForwardingConsumer {
-
-    private final Path outputPath;
-
-    /** Consumer that writes to {@param outputPath}. */
-    public FileConsumer(Path outputPath) {
-      this(outputPath, null);
-    }
-
-    /** Consumer that forwards to {@param consumer} and also writes to {@param outputPath}. */
-    public FileConsumer(Path outputPath, Utf8Consumer consumer) {
-      super(consumer);
-      this.outputPath = outputPath;
-    }
-
-    @Override
-    public void accept(byte[] data, DiagnosticsHandler handler) {
-      super.accept(data, handler);
-      try {
-        FileUtils.writeToFile(outputPath, null, data);
-      } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, new PathOrigin(outputPath)));
-      }
-    }
-  }
-
-  /**
-   * Stream consumer to write contents to an output stream.
-   *
-   * <p>Note: No close events are given to this stream so it should either be a permanent stream or
-   * the closing needs to happen outside of the compilation itself. If the stream is not one of the
-   * standard streams, i.e., System.out or System.err, you should likely implement yor own consumer.
-   */
-  class StreamConsumer extends ForwardingConsumer {
-
-    private final Origin origin;
-    private final OutputStream outputStream;
-
-    /** Consumer that writes to {@param outputStream}. */
-    public StreamConsumer(Origin origin, OutputStream outputStream) {
-      this(origin, outputStream, null);
-    }
-
-    /** Consumer that forwards to {@param consumer} and also writes to {@param outputStream}. */
-    public StreamConsumer(Origin origin, OutputStream outputStream, Utf8Consumer consumer) {
-      super(consumer);
-      this.origin = origin;
-      this.outputStream = outputStream;
-    }
-
-    @Override
-    public void accept(byte[] data, DiagnosticsHandler handler) {
-      super.accept(data, handler);
-      try {
-        outputStream.write(data);
-      } catch (IOException e) {
-        handler.error(new IOExceptionDiagnostic(e, origin));
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 0e6c94d..f4056e7 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.utils.VersionProperties;
 
+/** Version of the D8/R8 library. */
 public final class Version {
 
   // This field is accessed from release scripts using simple pattern matching.
@@ -20,6 +21,7 @@
     System.out.println(VersionProperties.INSTANCE.getDescription());
   }
 
+  /** Is this a development version of the D8/R8 library. */
   public static boolean isDev() {
     return LABEL.endsWith("-dev") || VersionProperties.INSTANCE.isEngineering();
   }
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index 7c50696..17c4706 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -6,18 +6,20 @@
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.utils.AbortException;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.OutputMode;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Proguard + dx compatibility interface for r8.
@@ -170,10 +172,16 @@
     if (options.mainDexList != null) {
       builder.addMainDexListFiles(Paths.get(options.mainDexList));
     }
-    AndroidApp result = R8.runInternal(builder.build());
+
+    // Wrap the output consumer so we can count the number of output files.
+    CountOutputConsumer outputConsumer =
+        new CountOutputConsumer((DexIndexedConsumer) builder.getProgramConsumer());
+    builder.setProgramConsumer(outputConsumer);
+
+    R8.run(builder.build());
 
     if (!options.multiDex) {
-      if (result.getDexProgramResources().size() > 1) {
+      if (outputConsumer.count > 1) {
         throw new CompilationError(
             "Compilation result could not fit into a single dex file. "
                 + "Reduce the input-program size or run with --multi-dex enabled");
@@ -193,4 +201,20 @@
       System.exit(1);
     }
   }
+
+  private static class CountOutputConsumer extends DexIndexedConsumer.ForwardingConsumer {
+
+    int count = 0;
+
+    public CountOutputConsumer(DexIndexedConsumer consumer) {
+      super(consumer);
+    }
+
+    @Override
+    public synchronized void accept(int fileIndex, byte[] data, Set<String> descriptors,
+        DiagnosticsHandler handler) {
+      super.accept(fileIndex, data, descriptors, handler);
+      count++;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 450cdab..b6a3ef6 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.errors.CompilationError;
@@ -39,6 +40,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -237,8 +239,19 @@
     }
 
     void readSources() throws IOException, ResourceException {
-      readDexSources(inputApp.getDexProgramResources(), PROGRAM, programClasses);
-      readClassSources(inputApp.getClassProgramResources(), PROGRAM, programClasses);
+      Collection<ProgramResource> resources = inputApp.computeAllProgramResources();
+      List<ProgramResource> dexResources = new ArrayList<>(resources.size());
+      List<ProgramResource> cfResources = new ArrayList<>(resources.size());
+      for (ProgramResource resource : resources) {
+        if (resource.getKind() == Kind.DEX) {
+          dexResources.add(resource);
+        } else {
+          assert resource.getKind() == Kind.CF;
+          cfResources.add(resource);
+        }
+      }
+      readDexSources(dexResources, PROGRAM, programClasses);
+      readClassSources(cfResources, PROGRAM, programClasses);
     }
 
     private <T extends DexClass> ClassProvider<T> buildClassProvider(ClassKind classKind,
diff --git a/src/main/java/com/android/tools/r8/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index 03f2ad5..77a82df 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -15,8 +15,8 @@
   public static final int MIN_VDEX_VERSION = 10;
   public static final int MAX_VDEX_VERSION = 11;
 
-  // We apply Java 7 class file constraints on DEX files.
-  public static final int CORRESPONDING_CLASS_FILE_VERSION = 51;
+  // We apply Java 6 class file constraints on DEX files.
+  public static final int CORRESPONDING_CLASS_FILE_VERSION = 50;
 
   public static final int DEX_MAGIC_SIZE = 8;
 
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
index 249e7a1..7bcb4fc 100644
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
@@ -16,6 +16,7 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -159,13 +160,37 @@
     return null;
   }
 
-  private static Options parseArguments(String[] args) {
+  private static Options parseArguments(String[] args) throws IOException {
+    // We may have a single argument which is a parameter file path, prefixed with '@'.
+    if (args.length == 1 && args[0].startsWith("@")) {
+      // TODO(tamaskenez) Implement more sophisticated processing
+      // which is aligned with Blaze's
+      // com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor
+      Path paramsFile = Paths.get(args[0].substring(1));
+      List<String> argsList = new ArrayList<>();
+      for (String s : Files.readAllLines(paramsFile)) {
+        s = s.trim();
+        if (s.isEmpty()) {
+          continue;
+        }
+        // Trim optional enclosing single quotes. Unescaping omitted for now.
+        if (s.length() >= 2 && s.startsWith("'") && s.endsWith("'")) {
+          s = s.substring(1, s.length() - 1);
+        }
+        argsList.add(s);
+      }
+      args = argsList.toArray(new String[argsList.size()]);
+    }
+
     Options options = new Options();
     ParseContext context = new ParseContext(args);
     List<String> strings;
     String string;
     Boolean b;
     while (context.head() != null) {
+      if (context.head().startsWith("@")) {
+        throw new RuntimeException("A params file must be the only argument: " + context.head());
+      }
       strings = tryParseMulti(context, "--input");
       if (strings != null) {
         options.inputArchives.addAll(strings);
@@ -322,7 +347,7 @@
         if (options.inputArchives.size() != 1) {
           throw new RuntimeException("'--multidex=given_shard' requires exactly one --input.");
         }
-        singleFixedFileIndex = parseFileIndexFromShardFilename(options.inputArchives.get(0));
+        singleFixedFileIndex = parseFileIndexFromShardFilename(options.inputArchives.get(0)) - 1;
         break;
       case MINIMAL:
       case BEST_EFFORT:
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 58d8473..dba4842 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -236,7 +236,7 @@
           + registerNum * 7
           + name.hashCode() * 13
           + type.hashCode() * 17
-          + (signature == null ? 0 : signature.hashCode()) * 19;
+          + Objects.hashCode(signature) * 19;
     }
 
     @Override
@@ -254,7 +254,7 @@
       if (!type.equals(o.type)) {
         return false;
       }
-      return (signature == o.signature || signature.equals(o.signature));
+      return Objects.equals(signature, o.signature);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index d4a2ef9..a2078f8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -130,6 +130,7 @@
   public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
   public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
   public final DexString methodHandleDescriptor = createString("Ljava/lang/invoke/MethodHandle;");
+  public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
 
   public final DexString intFieldUpdaterDescriptor =
       createString("Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;");
@@ -181,9 +182,12 @@
 
   public final DexType varHandleType = createType(varHandleDescriptor);
   public final DexType methodHandleType = createType(methodHandleDescriptor);
+  public final DexType methodTypeType = createType(methodTypeDescriptor);
 
-  public final StringBuildingMethods stringBuilderMethods = new StringBuildingMethods(stringBuilderType);
-  public final StringBuildingMethods stringBufferMethods = new StringBuildingMethods(stringBufferType);
+  public final StringBuildingMethods stringBuilderMethods =
+      new StringBuildingMethods(stringBuilderType);
+  public final StringBuildingMethods stringBufferMethods =
+      new StringBuildingMethods(stringBufferType);
   public final ObjectsMethods objectsMethods = new ObjectsMethods();
   public final ObjectMethods objectMethods = new ObjectMethods();
   public final LongMethods longMethods = new LongMethods();
@@ -239,7 +243,7 @@
     public final DexMethod getClass;
 
     private ObjectMethods() {
-      getClass = createMethod(objectsDescriptor, getClassMethodName, classDescriptor,
+      getClass = createMethod(objectDescriptor, getClassMethodName, classDescriptor,
           DexString.EMPTY_ARRAY);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 1cd1b20..2d45a308 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -4,8 +4,11 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import java.util.function.Function;
 
 public class ConstMethodHandle extends ConstInstruction {
 
@@ -76,4 +79,10 @@
   public ConstMethodHandle asConstMethodHandle() {
     return this;
   }
+
+  @Override
+  public TypeLatticeElement evaluate(
+      AppInfoWithSubtyping appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
+    return TypeLatticeElement.fromDexType(appInfo, appInfo.dexItemFactory.methodHandleType, false);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index cbcf143..aff7b9b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -4,8 +4,11 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import java.util.function.Function;
 
 public class ConstMethodType extends ConstInstruction {
 
@@ -76,4 +79,10 @@
   public ConstMethodType asConstMethodType() {
     return this;
   }
+
+  @Override
+  public TypeLatticeElement evaluate(
+      AppInfoWithSubtyping appInfo, Function<Value, TypeLatticeElement> getLatticeElement) {
+    return TypeLatticeElement.fromDexType(appInfo, appInfo.dexItemFactory.methodTypeType, false);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 9e6d79c..e21a9fa 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -85,8 +85,17 @@
     return new Builder(app);
   }
 
-  /** Get input streams for all dex program resources. */
-  public List<ProgramResource> getDexProgramResources() throws IOException {
+  /** Get full collection of all program resources from all program providers. */
+  public Collection<ProgramResource> computeAllProgramResources() throws ResourceException {
+    List<ProgramResource> resources = new ArrayList<>();
+    for (ProgramResourceProvider provider : programResourceProviders) {
+      resources.addAll(provider.getProgramResources());
+    }
+    return resources;
+  }
+
+  // TODO(zerny): Remove this method.
+  public List<ProgramResource> getDexProgramResourcesForTesting() throws IOException {
     try {
       return filter(programResourceProviders, Kind.DEX);
     } catch (ResourceException e) {
@@ -98,8 +107,8 @@
     }
   }
 
-  /** Get input streams for all Java-bytecode program resources. */
-  public List<ProgramResource> getClassProgramResources() throws IOException {
+  // TODO(zerny): Remove this method.
+  public List<ProgramResource> getClassProgramResourcesForTesting() throws IOException {
     try {
       return filter(programResourceProviders, Kind.CF);
     } catch (ResourceException e) {
@@ -192,7 +201,7 @@
    * Write the dex program resources and proguard resource to @code{directory}.
    */
   public void writeToDirectory(Path directory, OutputMode outputMode) throws IOException {
-    List<ProgramResource> dexProgramSources = getDexProgramResources();
+    List<ProgramResource> dexProgramSources = getDexProgramResourcesForTesting();
     if (outputMode.isDexIndexed()) {
       DexIndexedConsumer.DirectoryConsumer.writeResources(directory, dexProgramSources);
     } else {
@@ -205,7 +214,7 @@
    * Write the dex program resources to @code{archive} and the proguard resource as its sibling.
    */
   public void writeToZip(Path archive, OutputMode outputMode) throws IOException {
-    List<ProgramResource> resources = getDexProgramResources();
+    List<ProgramResource> resources = getDexProgramResourcesForTesting();
     if (outputMode.isDexIndexed()) {
       DexIndexedConsumer.ArchiveConsumer.writeResources(archive, resources);
     } else if (outputMode.isDexFilePerClassFile()) {
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
new file mode 100644
index 0000000..4e080b7
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -0,0 +1,498 @@
+// Copyright (c) 2017, 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.apiusagesample;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class D8ApiUsageSample {
+
+  private static final Origin origin =
+      new Origin(Origin.root()) {
+        @Override
+        public String part() {
+          return "D8ApiUsageSample";
+        }
+      };
+
+  private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
+
+  /**
+   * Example invocation:
+   *
+   * <pre>
+   *   java -jar d8-api-uses.jar \
+   *     --output path/to/output/dir \
+   *     --min-api minApiLevel \
+   *     --lib path/to/library.jar \
+   *     --classpath path/to/classpath.jar \
+   *     path/to/input{1,2,3}.{jar,class}
+   * </pre>
+   */
+  public static void main(String[] args) {
+    // Parse arguments with the commandline parser to make use of its API.
+    D8Command.Builder cmd = D8Command.parse(args, origin);
+    CompilationMode mode = cmd.getMode();
+    Path temp = cmd.getOutputPath();
+    int minApiLevel = cmd.getMinApiLevel();
+    // The Builder API does not provide access to the concrete paths
+    // (everything is put into providers) so manually parse them here.
+    List<Path> libraries = new ArrayList<>(1);
+    List<Path> classpath = new ArrayList<>(args.length);
+    List<Path> mainDexList = new ArrayList<>(1);
+    List<Path> inputs = new ArrayList<>(args.length);
+    for (int i = 0; i < args.length; i++) {
+      if (args[i].equals("--lib")) {
+        libraries.add(Paths.get(args[++i]));
+      } else if (args[i].equals("--classpath")) {
+        classpath.add(Paths.get(args[++i]));
+      } else if (args[i].equals("--main-dex-list")) {
+        mainDexList.add(Paths.get(args[++i]));
+      } else if (isArchive(args[i]) || isClassFile(args[i])) {
+        inputs.add(Paths.get(args[i]));
+      }
+    }
+    if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+      throw new RuntimeException("Must supply a temp/output directory");
+    }
+    if (inputs.isEmpty()) {
+      throw new RuntimeException("Must supply program inputs");
+    }
+    if (classpath.isEmpty()) {
+      throw new RuntimeException("Must supply classpath inputs");
+    }
+    if (libraries.isEmpty()) {
+      throw new RuntimeException("Must supply library inputs");
+    }
+    if (mainDexList.isEmpty()) {
+      throw new RuntimeException("Must supply main-dex-list inputs");
+    }
+
+    useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
+    useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
+    useProgramData(minApiLevel, libraries, classpath, inputs);
+    useProgramResourceProvider(minApiLevel, libraries, classpath, inputs);
+    useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
+    useMainDexListFiles(minApiLevel, libraries, classpath, inputs, mainDexList);
+    useMainDexClasses(minApiLevel, libraries, classpath, inputs, mainDexList);
+    useVArgVariants(minApiLevel, libraries, classpath, inputs, mainDexList);
+    incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
+  }
+
+  // Check API support for compiling Java class-files from the file system.
+  private static void useProgramFileList(
+      CompilationMode mode,
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs) {
+    try {
+      D8.run(
+          D8Command.builder(handler)
+              .setMode(mode)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addClasspathFiles(classpath)
+              .addProgramFiles(inputs)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  // Check API support for compiling Java class-files from byte content.
+  private static void useProgramData(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs) {
+    try {
+      D8Command.Builder builder =
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addClasspathFiles(classpath);
+      for (ClassFileContent classfile : readClassFiles(inputs)) {
+        builder.addClassProgramData(classfile.data, classfile.origin);
+      }
+      for (Path input : inputs) {
+        if (isDexFile(input)) {
+          builder.addDexProgramData(Files.readAllBytes(input), new PathOrigin(input));
+        }
+      }
+      D8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    } catch (IOException e) {
+      throw new RuntimeException("Unexpected IO exception", e);
+    }
+  }
+
+  // Check API support for compiling Java class-files from a program provider abstraction.
+  private static void useProgramResourceProvider(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs) {
+    try {
+      D8Command.Builder builder =
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addClasspathFiles(classpath);
+      for (Path input : inputs) {
+        if (isArchive(input)) {
+          builder.addProgramResourceProvider(
+              ArchiveProgramResourceProvider.fromArchive(
+                  input, ArchiveProgramResourceProvider::includeClassFileEntries));
+        } else if (isClassFile(input)) {
+          builder.addProgramResourceProvider(
+              new ProgramResourceProvider() {
+                @Override
+                public Collection<ProgramResource> getProgramResources() throws ResourceException {
+                  return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+                }
+              });
+        } else if (isDexFile(input)) {
+          builder.addProgramResourceProvider(
+              new ProgramResourceProvider() {
+                @Override
+                public Collection<ProgramResource> getProgramResources() throws ResourceException {
+                  return Collections.singleton(ProgramResource.fromFile(Kind.DEX, input));
+                }
+              });
+        }
+      }
+      D8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  private static void useLibraryAndClasspathProvider(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs) {
+    try {
+      D8Command.Builder builder =
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addProgramFiles(inputs);
+      for (Path library : libraries) {
+        builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+      }
+      for (Path path : classpath) {
+        builder.addClasspathResourceProvider(new ArchiveClassFileProvider(path));
+      }
+      D8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    } catch (IOException e) {
+      throw new RuntimeException("Unexpected IO exception", e);
+    }
+  }
+
+  private static void useMainDexListFiles(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs,
+      Collection<Path> mainDexList) {
+    try {
+      D8.run(
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addClasspathFiles(classpath)
+              .addProgramFiles(inputs)
+              .addMainDexListFiles(mainDexList)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  private static void useMainDexClasses(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs,
+      Collection<Path> mainDexList) {
+    try {
+      List<String> mainDexClasses = new ArrayList<>(1);
+      for (Path path : mainDexList) {
+        for (String line : Files.readAllLines(path)) {
+          String entry = line.trim();
+          if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
+            continue;
+          }
+          mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
+        }
+      }
+      D8.run(
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addClasspathFiles(classpath)
+              .addProgramFiles(inputs)
+              .addMainDexClasses(mainDexClasses)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    } catch (IOException e) {
+      throw new RuntimeException("Unexpected IO exception", e);
+    }
+  }
+
+  // Check API support for all the varg variants.
+  private static void useVArgVariants(
+      int minApiLevel,
+      List<Path> libraries,
+      List<Path> classpath,
+      List<Path> inputs,
+      List<Path> mainDexList) {
+    try {
+      D8.run(
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries.get(0))
+              .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
+              .addClasspathFiles(classpath.get(0))
+              .addClasspathFiles(classpath.stream().skip(1).toArray(Path[]::new))
+              .addProgramFiles(inputs.get(0))
+              .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
+              .addMainDexListFiles(mainDexList.get(0))
+              .addMainDexListFiles(mainDexList.stream().skip(1).toArray(Path[]::new))
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  private static void incrementalCompileAndMerge(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs) {
+    // Compile and merge via index intermediates.
+    mergeIntermediates(
+        minApiLevel, compileToIndexedIntermediates(minApiLevel, libraries, classpath, inputs));
+    // Compile and merge via per-classfile intermediates.
+    mergeIntermediates(
+        minApiLevel, compileToPerClassFileIntermediates(minApiLevel, libraries, classpath, inputs));
+  }
+
+  private static Collection<byte[]> compileToIndexedIntermediates(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs) {
+    IndexIntermediatesConsumer consumer = new IndexIntermediatesConsumer();
+    try {
+      D8.run(
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setIntermediate(true)
+              .setProgramConsumer(consumer)
+              .addClasspathFiles(classpath)
+              .addLibraryFiles(libraries)
+              .addProgramFiles(inputs)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+    return consumer.bytes;
+  }
+
+  private static Collection<byte[]> compileToPerClassFileIntermediates(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> classpath,
+      Collection<Path> inputs) {
+    PerClassIntermediatesConsumer consumer = new PerClassIntermediatesConsumer();
+    try {
+      D8.run(
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(consumer)
+              .addLibraryFiles(libraries)
+              .addClasspathFiles(classpath)
+              .addProgramFiles(inputs)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+    return consumer.bytes;
+  }
+
+  private static void mergeIntermediates(int minApiLevel, Collection<byte[]> intermediates) {
+    D8Command.Builder builder =
+        D8Command.builder(handler)
+            .setMinApiLevel(minApiLevel)
+            .setProgramConsumer(new EnsureOutputConsumer());
+    for (byte[] intermediate : intermediates) {
+      builder.addDexProgramData(intermediate, Origin.unknown());
+    }
+    try {
+      D8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected merging error", e);
+    }
+  }
+
+  // Helpers for tests.
+  // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+  // rely on it.
+
+  private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
+    List<ClassFileContent> classfiles = new ArrayList<>();
+    for (Path file : files) {
+      if (isArchive(file)) {
+        Origin zipOrigin = new PathOrigin(file);
+        ZipInputStream zip = new ZipInputStream(Files.newInputStream(file));
+        ZipEntry entry;
+        while (null != (entry = zip.getNextEntry())) {
+          if (isClassFile(Paths.get(entry.getName()))) {
+            Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin);
+            classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+          }
+        }
+      } else if (isClassFile(file)) {
+        classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+      }
+    }
+    return classfiles;
+  }
+
+  private static byte[] readBytes(InputStream stream) throws IOException {
+    try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+      byte[] buffer = new byte[0xffff];
+      for (int length; (length = stream.read(buffer)) != -1; ) {
+        bytes.write(buffer, 0, length);
+      }
+      return bytes.toByteArray();
+    }
+  }
+
+  private static boolean isClassFile(Path file) {
+    return isClassFile(file.toString());
+  }
+
+  private static boolean isClassFile(String file) {
+    file = file.toLowerCase();
+    return file.endsWith(".class");
+  }
+
+  private static boolean isDexFile(Path file) {
+    return isDexFile(file.toString());
+  }
+
+  private static boolean isDexFile(String file) {
+    file = file.toLowerCase();
+    return file.endsWith(".dex");
+  }
+
+  private static boolean isArchive(Path file) {
+    return isArchive(file.toString());
+  }
+
+  private static boolean isArchive(String file) {
+    file = file.toLowerCase();
+    return file.endsWith(".zip") || file.endsWith(".jar");
+  }
+
+  private static class ClassFileContent {
+    final Origin origin;
+    final byte[] data;
+
+    public ClassFileContent(Origin origin, byte[] data) {
+      this.origin = origin;
+      this.data = data;
+    }
+  }
+
+  private static class IndexIntermediatesConsumer implements DexIndexedConsumer {
+
+    List<byte[]> bytes = new ArrayList<>();
+
+    @Override
+    public synchronized void accept(
+        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+      bytes.add(data);
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {}
+  }
+
+  private static class PerClassIntermediatesConsumer implements DexFilePerClassFileConsumer {
+
+    List<byte[]> bytes = new ArrayList<>();
+
+    @Override
+    public synchronized void accept(
+        String primaryClassDescriptor,
+        byte[] data,
+        Set<String> descriptors,
+        DiagnosticsHandler handler) {
+      bytes.add(data);
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {}
+  }
+
+  private static class EnsureOutputConsumer implements DexIndexedConsumer {
+    boolean hasOutput = false;
+
+    @Override
+    public synchronized void accept(
+        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+      hasOutput = true;
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      if (!hasOutput) {
+        handler.error(new StringDiagnostic("Expected to produce output but had none"));
+      }
+    }
+  }
+}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
index d9f11b0..9b08352 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
@@ -49,7 +49,7 @@
     Origin origin = diagnostic.getOrigin();
     Position positionInOrigin = diagnostic.getPosition();
     String position;
-    if (origin instanceof PathOrigin) {
+    if (origin.parent() instanceof PathOrigin) {
       if (positionInOrigin instanceof TextRange) {
         TextRange textRange = (TextRange) positionInOrigin;
         position = ((PathOrigin) origin.parent()).getPath().toFile() + ": "
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
new file mode 100644
index 0000000..7230f55
--- /dev/null
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -0,0 +1,450 @@
+// Copyright (c) 2018, 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.apiusagesample;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class R8ApiUsageSample {
+
+  private static final Origin origin =
+      new Origin(Origin.root()) {
+        @Override
+        public String part() {
+          return "R8ApiUsageSample";
+        }
+      };
+
+  private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
+
+  /**
+   * Example invocation:
+   *
+   * <pre>
+   *   java -jar r8-api-uses.jar \
+   *     --output path/to/output/dir \
+   *     --min-api minApiLevel \
+   *     --lib path/to/library.jar \
+   *     path/to/input{1,2,3}.{jar,class}
+   * </pre>
+   */
+  public static void main(String[] args) {
+    // Parse arguments with the commandline parser to make use of its API.
+    R8Command.Builder cmd = R8Command.parse(args, origin);
+    CompilationMode mode = cmd.getMode();
+    Path temp = cmd.getOutputPath();
+    int minApiLevel = cmd.getMinApiLevel();
+    // The Builder API does not provide access to the concrete paths
+    // (everything is put into providers) so manually parse them here.
+    List<Path> libraries = new ArrayList<>(1);
+    List<Path> mainDexList = new ArrayList<>(1);
+    List<Path> mainDexRules = new ArrayList<>(1);
+    List<Path> pgConf = new ArrayList<>(1);
+    List<Path> inputs = new ArrayList<>(args.length);
+    for (int i = 0; i < args.length; i++) {
+      if (args[i].equals("--lib")) {
+        libraries.add(Paths.get(args[++i]));
+      } else if (args[i].equals("--main-dex-list")) {
+        mainDexList.add(Paths.get(args[++i]));
+      } else if (args[i].equals("--main-dex-rules")) {
+        mainDexRules.add(Paths.get(args[++i]));
+      } else if (args[i].equals("--pg-conf")) {
+        pgConf.add(Paths.get(args[++i]));
+      } else if (isArchive(args[i]) || isClassFile(args[i])) {
+        inputs.add(Paths.get(args[i]));
+      }
+    }
+    if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+      throw new RuntimeException("Must supply a temp/output directory");
+    }
+    if (inputs.isEmpty()) {
+      throw new RuntimeException("Must supply program inputs");
+    }
+    if (libraries.isEmpty()) {
+      throw new RuntimeException("Must supply library inputs");
+    }
+    if (mainDexList.isEmpty()) {
+      throw new RuntimeException("Must supply main-dex-list inputs");
+    }
+    if (mainDexRules.isEmpty()) {
+      throw new RuntimeException("Must supply main-dex-rules inputs");
+    }
+    if (pgConf.isEmpty()) {
+      throw new RuntimeException("Must supply pg-conf inputs");
+    }
+
+    useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, inputs);
+    useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, inputs);
+    useProgramData(minApiLevel, libraries, inputs);
+    useProgramResourceProvider(minApiLevel, libraries, inputs);
+    useLibraryResourceProvider(minApiLevel, libraries, inputs);
+    useMainDexListFiles(minApiLevel, libraries, inputs, mainDexList);
+    useMainDexClasses(minApiLevel, libraries, inputs, mainDexList);
+    useMainDexRulesFiles(minApiLevel, libraries, inputs, mainDexRules);
+    useMainDexRules(minApiLevel, libraries, inputs, mainDexRules);
+    useProguardConfigFiles(minApiLevel, libraries, inputs, mainDexList, pgConf);
+    useProguardConfigLines(minApiLevel, libraries, inputs, mainDexList, pgConf);
+    useVArgVariants(minApiLevel, libraries, inputs, mainDexList, mainDexRules, pgConf);
+  }
+
+  // Check API support for compiling Java class-files from the file system.
+  private static void useProgramFileList(
+      CompilationMode mode, int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+    try {
+      R8.run(
+          R8Command.builder(handler)
+              .setMode(mode)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addProgramFiles(inputs)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  // Check API support for compiling Java class-files from byte content.
+  private static void useProgramData(
+      int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+    try {
+      R8Command.Builder builder =
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries);
+      for (ClassFileContent classfile : readClassFiles(inputs)) {
+        builder.addClassProgramData(classfile.data, classfile.origin);
+      }
+      R8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    } catch (IOException e) {
+      throw new RuntimeException("Unexpected IO exception", e);
+    }
+  }
+
+  // Check API support for compiling Java class-files from a program provider abstraction.
+  private static void useProgramResourceProvider(
+      int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+    try {
+      R8Command.Builder builder =
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries);
+      for (Path input : inputs) {
+        if (isArchive(input)) {
+          builder.addProgramResourceProvider(
+              ArchiveProgramResourceProvider.fromArchive(
+                  input, ArchiveProgramResourceProvider::includeClassFileEntries));
+        } else {
+          builder.addProgramResourceProvider(
+              new ProgramResourceProvider() {
+                @Override
+                public Collection<ProgramResource> getProgramResources() throws ResourceException {
+                  return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+                }
+              });
+        }
+      }
+      R8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  private static void useLibraryResourceProvider(
+      int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+    try {
+      R8Command.Builder builder =
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addProgramFiles(inputs);
+      for (Path library : libraries) {
+        builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+      }
+      R8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    } catch (IOException e) {
+      throw new RuntimeException("Unexpected IO exception", e);
+    }
+  }
+
+  private static void useMainDexListFiles(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> inputs,
+      Collection<Path> mainDexList) {
+    try {
+      R8.run(
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addProgramFiles(inputs)
+              .addMainDexListFiles(mainDexList)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  private static void useMainDexClasses(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> inputs,
+      Collection<Path> mainDexList) {
+    try {
+      List<String> mainDexClasses = new ArrayList<>(1);
+      for (Path path : mainDexList) {
+        for (String line : Files.readAllLines(path)) {
+          String entry = line.trim();
+          if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
+            continue;
+          }
+          mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
+        }
+      }
+      R8.run(
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addProgramFiles(inputs)
+              .addMainDexClasses(mainDexClasses)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    } catch (IOException e) {
+      throw new RuntimeException("Unexpected IO exception", e);
+    }
+  }
+
+  private static void useMainDexRulesFiles(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> inputs,
+      Collection<Path> mainDexRules) {
+    try {
+      R8.run(
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addProgramFiles(inputs)
+              .addMainDexRulesFiles(mainDexRules)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  private static void useMainDexRules(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> inputs,
+      Collection<Path> mainDexRulesFiles) {
+    try {
+      R8Command.Builder builder =
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addProgramFiles(inputs);
+      for (Path mainDexRulesFile : mainDexRulesFiles) {
+        builder.addMainDexRules(
+            Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
+      }
+      R8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    } catch (IOException e) {
+      throw new RuntimeException("Unexpected IO exception", e);
+    }
+  }
+
+  private static void useProguardConfigFiles(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> inputs,
+      Collection<Path> mainDexList,
+      List<Path> pgConf) {
+    try {
+      R8.run(
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addProgramFiles(inputs)
+              .addMainDexListFiles(mainDexList)
+              .addProguardConfigurationFiles(pgConf)
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  private static void useProguardConfigLines(
+      int minApiLevel,
+      Collection<Path> libraries,
+      Collection<Path> inputs,
+      Collection<Path> mainDexList,
+      List<Path> pgConf) {
+    try {
+      R8Command.Builder builder =
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries)
+              .addProgramFiles(inputs)
+              .addMainDexListFiles(mainDexList);
+      for (Path file : pgConf) {
+        builder.addProguardConfiguration(Files.readAllLines(file), new PathOrigin(file));
+      }
+      R8.run(builder.build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    } catch (IOException e) {
+      throw new RuntimeException("Unexpected IO exception", e);
+    }
+  }
+
+  // Check API support for all the varg variants.
+  private static void useVArgVariants(
+      int minApiLevel,
+      List<Path> libraries,
+      List<Path> inputs,
+      List<Path> mainDexList,
+      List<Path> mainDexRules,
+      List<Path> pgConf) {
+    try {
+      R8.run(
+          R8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .addLibraryFiles(libraries.get(0))
+              .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
+              .addProgramFiles(inputs.get(0))
+              .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
+              .addMainDexListFiles(mainDexList.get(0))
+              .addMainDexListFiles(mainDexList.stream().skip(1).toArray(Path[]::new))
+              .addMainDexRulesFiles(mainDexRules.get(0))
+              .addMainDexRulesFiles(mainDexRules.stream().skip(1).toArray(Path[]::new))
+              .addProguardConfigurationFiles(pgConf.get(0))
+              .addProguardConfigurationFiles(pgConf.stream().skip(1).toArray(Path[]::new))
+              .build());
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException("Unexpected compilation exceptions", e);
+    }
+  }
+
+  // Helpers for tests.
+  // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+  // rely on it.
+
+  private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
+    List<ClassFileContent> classfiles = new ArrayList<>();
+    for (Path file : files) {
+      if (isArchive(file)) {
+        Origin zipOrigin = new PathOrigin(file);
+        ZipInputStream zip = new ZipInputStream(Files.newInputStream(file));
+        ZipEntry entry;
+        while (null != (entry = zip.getNextEntry())) {
+          if (isClassFile(Paths.get(entry.getName()))) {
+            Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin);
+            classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+          }
+        }
+      } else if (isClassFile(file)) {
+        classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+      }
+    }
+    return classfiles;
+  }
+
+  private static byte[] readBytes(InputStream stream) throws IOException {
+    try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+      byte[] buffer = new byte[0xffff];
+      for (int length; (length = stream.read(buffer)) != -1; ) {
+        bytes.write(buffer, 0, length);
+      }
+      return bytes.toByteArray();
+    }
+  }
+
+  private static boolean isClassFile(Path file) {
+    return isClassFile(file.toString());
+  }
+
+  private static boolean isClassFile(String file) {
+    file = file.toLowerCase();
+    return file.endsWith(".class");
+  }
+
+  private static boolean isArchive(Path file) {
+    return isArchive(file.toString());
+  }
+
+  private static boolean isArchive(String file) {
+    file = file.toLowerCase();
+    return file.endsWith(".zip") || file.endsWith(".jar");
+  }
+
+  private static class ClassFileContent {
+    final Origin origin;
+    final byte[] data;
+
+    public ClassFileContent(Origin origin, byte[] data) {
+      this.origin = origin;
+      this.data = data;
+    }
+  }
+
+  private static class EnsureOutputConsumer implements DexIndexedConsumer {
+    boolean hasOutput = false;
+
+    @Override
+    public synchronized void accept(
+        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+      hasOutput = true;
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      if (!hasOutput) {
+        handler.error(new StringDiagnostic("Expected to produce output but had none"));
+      }
+    }
+  }
+}
diff --git a/src/test/examples/staticinlining/Main.java b/src/test/examples/staticinlining/Main.java
new file mode 100644
index 0000000..dfbafa6
--- /dev/null
+++ b/src/test/examples/staticinlining/Main.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2018, 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 staticinlining;
+
+public class Main {
+  public static String tag = null;
+
+  public static void printMessage(String msg) {
+    System.out.println(msg);
+  }
+
+  public static void test() {
+    tag = "Sub1";
+    Sub1.inlineMe();  // triggers class init of Sub1 (and its hierarchy)
+    tag = "Sub2";
+    Sub2.doNotInlineMe();  // triggers class init of Sub2 (and its hierarchy)
+    printMessage("SuperClass.INIT_TAG=" + SuperClass.INIT_TAG);
+  }
+
+  public static void main(String[] args) {
+    test();
+  }
+
+}
diff --git a/src/test/examples/staticinlining/Sub1.java b/src/test/examples/staticinlining/Sub1.java
new file mode 100644
index 0000000..2e06601
--- /dev/null
+++ b/src/test/examples/staticinlining/Sub1.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2018, 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 staticinlining;
+
+import inlining.AlwaysInline;
+
+public class Sub1 extends SuperClass {
+
+  @AlwaysInline
+  public static void inlineMe() {
+    Main.printMessage("Sub1");
+  }
+
+}
diff --git a/src/test/examples/staticinlining/Sub2.java b/src/test/examples/staticinlining/Sub2.java
new file mode 100644
index 0000000..e253f79
--- /dev/null
+++ b/src/test/examples/staticinlining/Sub2.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, 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 staticinlining;
+
+public class Sub2 extends SuperClass {
+
+  public static void doNotInlineMe() {
+    System.out.println("Do not inline me 1");
+    System.out.println("Do not inline me 2");
+    System.out.println("Do not inline me 3");
+    System.out.println("Do not inline me 4");
+    System.out.println("Do not inline me 5");
+    System.out.println("Do not inline me 6");
+    System.out.println("Do not inline me 7");
+    System.out.println("Do not inline me 8");
+    System.out.println("Do not inline me 9");
+    System.out.println("Do not inline me 10");
+  }
+
+}
diff --git a/src/test/examples/staticinlining/SuperClass.java b/src/test/examples/staticinlining/SuperClass.java
new file mode 100644
index 0000000..237c11d
--- /dev/null
+++ b/src/test/examples/staticinlining/SuperClass.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, 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 staticinlining;
+
+public class SuperClass {
+  public static final String INIT_TAG = Main.tag;
+}
diff --git a/src/test/examples/staticinlining/keep-rules.txt b/src/test/examples/staticinlining/keep-rules.txt
new file mode 100644
index 0000000..1de091f
--- /dev/null
+++ b/src/test/examples/staticinlining/keep-rules.txt
@@ -0,0 +1,13 @@
+# Copyright (c) 2018, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class staticinlining.Main {
+  public static void main(...);
+}
+
+-alwaysinline class * {
+  @inlining.AlwaysInline <methods>;
+}
diff --git a/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
deleted file mode 100644
index 5a3b0f8..0000000
--- a/src/test/java/com/android/tools/r8/D8APiBinaryCompatibilityTests.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2017, 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 static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableList;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-public class D8APiBinaryCompatibilityTests {
-
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
-  @Test
-  public void testCompatibility() throws IOException {
-    Path compilerJar = Paths.get("tests", "api_usage_sample.jar");
-    String compiler = "com.android.tools.apiusagesample.D8Compiler";
-
-    String output = temp.newFolder().getAbsolutePath();
-    int minSdkVersion = AndroidApiLevel.K.getLevel();
-    String androidJar = ToolHelper.getAndroidJar(minSdkVersion);
-    Path lib1 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
-        "desugaringwithmissingclasslib1" + JAR_EXTENSION);
-    Path lib2 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
-        "desugaringwithmissingclasslib2" + JAR_EXTENSION);
-    Path input = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
-        "classes", "desugaringwithmissingclasstest1");
-    File mainDexClasses = temp.newFile();
-    Files.asCharSink(mainDexClasses, StandardCharsets.UTF_8)
-        .write("desugaringwithmissingclasstest1/Main.class");
-
-    List<String> command = ImmutableList.of(
-        ToolHelper.getJavaExecutable(),
-        "-cp",
-        compilerJar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
-        compiler,
-        // Compiler arguments.
-        output,
-        input.toString(),
-        Integer.toString(minSdkVersion),
-        mainDexClasses.getAbsolutePath(),
-        androidJar,
-        lib1.toString(),
-        lib2.toString());
-    ProcessBuilder builder = new ProcessBuilder(command);
-    ProcessResult result = ToolHelper.runProcess(builder);
-
-    Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
new file mode 100644
index 0000000..86f0319
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2017, 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 static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class D8ApiBinaryCompatibilityTests {
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void testCompatibilityDeprecatedApi() throws IOException {
+    Path compilerJar = Paths.get("tests", "api_usage_sample.jar");
+    String compiler = "com.android.tools.apiusagesample.D8Compiler";
+    String output = temp.newFolder().getAbsolutePath();
+    int minSdkVersion = AndroidApiLevel.K.getLevel();
+    String androidJar = ToolHelper.getAndroidJar(minSdkVersion);
+    Path lib1 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+        "desugaringwithmissingclasslib1" + JAR_EXTENSION);
+    Path lib2 = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+        "desugaringwithmissingclasslib2" + JAR_EXTENSION);
+    Path input = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+        "classes", "desugaringwithmissingclasstest1");
+    File mainDexClasses = temp.newFile();
+    Files.asCharSink(mainDexClasses, StandardCharsets.UTF_8)
+        .write("desugaringwithmissingclasstest1/Main.class");
+
+    List<String> command = ImmutableList.of(
+        ToolHelper.getJavaExecutable(),
+        "-cp",
+        compilerJar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+        compiler,
+        // Compiler arguments.
+        output,
+        input.toString(),
+        Integer.toString(minSdkVersion),
+        mainDexClasses.getAbsolutePath(),
+        androidJar,
+        lib1.toString(),
+        lib2.toString());
+    ProcessBuilder builder = new ProcessBuilder(command);
+    ProcessResult result = ToolHelper.runProcess(builder);
+    Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+    Assert.assertTrue(result.stdout, result.stdout.isEmpty());
+    Assert.assertTrue(result.stderr, result.stderr.isEmpty());
+  }
+
+  @Test
+  public void testCompatibilityNewApi() throws IOException {
+    Path jar = Paths.get("tests", "d8_api_usage_sample.jar");
+    String main = "com.android.tools.apiusagesample.D8ApiUsageSample";
+    int minApiLevel = AndroidApiLevel.K.getLevel();
+
+    Path lib1 =
+        Paths.get(
+            ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+            "desugaringwithmissingclasslib1" + JAR_EXTENSION);
+    Path lib2 =
+        Paths.get(
+            ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
+            "desugaringwithmissingclasslib2" + JAR_EXTENSION);
+    Path inputDir =
+        Paths.get(
+            ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR, "classes", "desugaringwithmissingclasstest1");
+    List<Path> input =
+        ImmutableList.of(
+            inputDir.resolve("ImplementMethodsWithDefault.class"), inputDir.resolve("Main.class"));
+
+    Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
+    FileUtils.writeTextFile(mainDexList, "desugaringwithmissingclasstest1/Main.class");
+
+    List<String> command =
+        ImmutableList.<String>builder()
+            .addAll(
+                ImmutableList.of(
+                    ToolHelper.getJavaExecutable(),
+                    "-cp",
+                    jar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+                    main,
+                    // Compiler arguments.
+                    "--output",
+                    temp.newFolder().getAbsolutePath(),
+                    "--min-api",
+                    Integer.toString(minApiLevel),
+                    "--main-dex-list",
+                    mainDexList.toString(),
+                    "--lib",
+                    ToolHelper.getAndroidJar(minApiLevel),
+                    "--classpath",
+                    lib1.toString(),
+                    "--classpath",
+                    lib2.toString()))
+            .addAll(input.stream().map(Path::toString).collect(Collectors.toList()))
+            .build();
+
+    ProcessBuilder builder = new ProcessBuilder(command);
+    ProcessResult result = ToolHelper.runProcess(builder);
+    Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+    Assert.assertTrue(result.stdout, result.stdout.isEmpty());
+    Assert.assertTrue(result.stderr, result.stderr.isEmpty());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 7eb73df..f263d4e 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -23,6 +23,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.Set;
 import java.util.zip.ZipFile;
 import org.junit.Ignore;
 import org.junit.Rule;
@@ -58,8 +59,8 @@
     assertEquals(AndroidVersion.DEFAULT.getApiLevel(), command.getMinApiLevel());
     assertTrue(command.getProgramConsumer() instanceof DexIndexedConsumer);
     AndroidApp app = ToolHelper.getApp(command);
-    assertEquals(0, app.getDexProgramResources().size());
-    assertEquals(0, app.getClassProgramResources().size());
+    assertEquals(0, app.getDexProgramResourcesForTesting().size());
+    assertEquals(0, app.getClassProgramResourcesForTesting().size());
   }
 
   @Test
@@ -257,7 +258,10 @@
   @Test(expected = CompilationFailedException.class)
   public void vdexFileUnsupported() throws Throwable {
     Path vdexFile = temp.newFile("test.vdex").toPath();
-    D8Command.builder().addProgramFiles(vdexFile).build();
+    D8Command.builder()
+        .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+        .addProgramFiles(vdexFile)
+        .build();
   }
 
   @Test
@@ -294,6 +298,31 @@
     }
   }
 
+  @Test(expected = CompilationFailedException.class)
+  public void addMultiTypeProgramConsumer() throws CompilationFailedException {
+    class MultiTypeConsumer implements DexIndexedConsumer, DexFilePerClassFileConsumer {
+
+      @Override
+      public void accept(String primaryClassDescriptor, byte[] data, Set<String> descriptors,
+          DiagnosticsHandler handler) {
+
+      }
+
+      @Override
+      public void accept(int fileIndex, byte[] data, Set<String> descriptors,
+          DiagnosticsHandler handler) {
+
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {
+
+      }
+    }
+
+    D8Command.builder().setProgramConsumer(new MultiTypeConsumer()).build();
+  }
+
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 29ece33..8cfdee8 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -65,10 +65,10 @@
       for (String classFile : classFiles) {
         AndroidApp app = compileClassFiles(
             testJarFile, Collections.singletonList(classFile), null, OutputMode.Indexed);
-        assert app.getDexProgramResources().size() == 1;
+        assert app.getDexProgramResourcesForTesting().size() == 1;
         fileToResource.put(
             makeRelative(testJarFile, Paths.get(classFile)).toString(),
-            app.getDexProgramResources().get(0));
+            app.getDexProgramResourcesForTesting().get(0));
       }
       return fileToResource;
     }
@@ -80,7 +80,7 @@
       List<String> classFiles = collectClassFiles(testJarFile);
       AndroidApp app = compileClassFiles(
           testJarFile, classFiles, output, OutputMode.FilePerInputClass);
-      for (ProgramResource resource : app.getDexProgramResources()) {
+      for (ProgramResource resource : app.getDexProgramResourcesForTesting()) {
         Set<String> descriptors = resource.getClassDescriptors();
         String mainClassDescriptor = app.getPrimaryClassDescriptor(resource);
         Assert.assertNotNull(mainClassDescriptor);
@@ -197,8 +197,8 @@
       D8Command command = builder.build();
       try {
         AndroidApp app = ToolHelper.runD8(command, this::combinedOptionConsumer);
-        assert app.getDexProgramResources().size() == 1;
-        return app.getDexProgramResources().get(0);
+        assert app.getDexProgramResourcesForTesting().size() == 1;
+        return app.getDexProgramResourcesForTesting().get(0);
       } catch (Unimplemented | CompilationError | InternalCompilerError re) {
         throw re;
       } catch (RuntimeException re) {
diff --git a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
index 2c723ff..36f92b4 100644
--- a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
@@ -110,13 +110,13 @@
                 options.interfaceMethodDesugaring = OffOrAuto.Auto;
                 options.setMarker(null);
               });
-      individalDexes.add(individualResult.getDexProgramResources().get(0));
+      individalDexes.add(individualResult.getDexProgramResourcesForTesting().get(0));
     }
     AndroidApp mergedResult = mergeDexResources(minAPILevel, individalDexes);
 
     assertTrue(Arrays.equals(
-        readResource(fullBuildResult.getDexProgramResources().get(0)),
-        readResource(mergedResult.getDexProgramResources().get(0))));
+        readResource(fullBuildResult.getDexProgramResourcesForTesting().get(0)),
+        readResource(mergedResult.getDexProgramResourcesForTesting().get(0))));
   }
 
   private AndroidApp mergeDexResources(int minAPILevel, List<Resource> individalDexes)
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
new file mode 100644
index 0000000..898e691
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2018, 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.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class R8ApiBinaryCompatibilityTests {
+
+  static final Path JAR = Paths.get("tests", "r8_api_usage_sample.jar");
+  static final String MAIN = "com.android.tools.apiusagesample.R8ApiUsageSample";
+  static final int MIN_API = AndroidApiLevel.K.getLevel();
+
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void testCompatibility() throws IOException {
+    List<Path> inputs =
+        ImmutableList.of(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "arithmetic.jar"));
+
+    Path pgConf = temp.getRoot().toPath().resolve("pg.conf");
+    FileUtils.writeTextFile(
+        pgConf, TestBase.keepMainProguardConfiguration("arithmetic.Arithmetic"));
+
+    Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
+    FileUtils.writeTextFile(
+        mainDexRules, TestBase.keepMainProguardConfiguration("arithmetic.Arithmetic"));
+
+    Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
+    FileUtils.writeTextFile(mainDexList, "arithmetic/Arithmetic.class");
+
+    List<String> command =
+        ImmutableList.<String>builder()
+            .addAll(
+                ImmutableList.of(
+                    ToolHelper.getJavaExecutable(),
+                    "-cp",
+                    JAR.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+                    MAIN,
+                    // Compiler arguments.
+                    "--output",
+                    temp.newFolder().toString(),
+                    "--min-api",
+                    Integer.toString(MIN_API),
+                    "--pg-conf",
+                    pgConf.toString(),
+                    "--main-dex-rules",
+                    mainDexRules.toString(),
+                    "--main-dex-list",
+                    mainDexList.toString(),
+                    "--lib",
+                    ToolHelper.getAndroidJar(MIN_API)))
+            .addAll(inputs.stream().map(Path::toString).collect(Collectors.toList()))
+            .build();
+
+    ProcessBuilder builder = new ProcessBuilder(command);
+    ProcessResult result = ToolHelper.runProcess(builder);
+    Assert.assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
+    Assert.assertTrue(result.stdout, result.stdout.isEmpty());
+    Assert.assertTrue(result.stderr, result.stderr.isEmpty());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java b/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
index 8c9d2be..ae44a5a 100644
--- a/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
@@ -17,7 +17,8 @@
 
 public class R8CodeCanonicalizationTest {
 
-  private static final String SOURCE_DEX = "invokeempty/classes.dex";
+  private static final Path SOURCE_DEX = Paths.get(
+      ToolHelper.EXAMPLES_BUILD_DIR, "invokeempty", "classes.dex");
 
   private int readNumberOfCodes(Path file) throws IOException {
     Segment[] segments = DexFileReader.parseMapFrom(file);
@@ -34,13 +35,12 @@
 
   @Test
   public void testNumberOfCodeItemsUnchanged() throws Exception {
-    int numberOfCodes = readNumberOfCodes(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + SOURCE_DEX));
-    R8.run(
-        R8Command.builder()
-            .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + SOURCE_DEX))
-            .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
-            .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed)
-            .build());
+    int numberOfCodes = readNumberOfCodes(SOURCE_DEX);
+    R8Command.Builder builder = R8Command.builder()
+        .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
+        .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed);
+    ToolHelper.getAppBuilder(builder).addProgramFiles(SOURCE_DEX);
+    R8.run(builder.build());
 
     int newNumberOfCodes = readNumberOfCodes(
         Paths.get(temp.getRoot().getCanonicalPath(), "classes.dex"));
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 9f4a0b1..1e79404 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -6,16 +6,22 @@
 import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.OutputMode;
 import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import org.junit.Rule;
 import org.junit.Test;
@@ -23,6 +29,44 @@
 
 public class R8CommandTest {
 
+  // Helper to check that a particular error occurred.
+  static class DiagnosticsChecker implements DiagnosticsHandler {
+    public List<Diagnostic> errors = new ArrayList<>();
+    public List<Diagnostic> warnings = new ArrayList<>();
+    public List<Diagnostic> infos = new ArrayList<>();
+
+    @Override
+    public void error(Diagnostic error) {
+      errors.add(error);
+    }
+
+    @Override
+    public void warning(Diagnostic warning) {
+      warnings.add(warning);
+    }
+
+    @Override
+    public void info(Diagnostic info) {
+      infos.add(info);
+    }
+
+    public interface FailingRunner {
+      void run(DiagnosticsHandler handler) throws CompilationFailedException;
+    }
+
+    public static void checkErrorsContains(String snippet, FailingRunner runner)
+        throws CompilationFailedException {
+      DiagnosticsChecker handler = new DiagnosticsChecker();
+      try {
+        runner.run(handler);
+      } catch (CompilationFailedException e) {
+        assertTrue(handler.errors.stream()
+            .anyMatch(d -> d.getDiagnosticMessage().contains(snippet)));
+        throw e;
+      }
+    }
+  }
+
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
@@ -47,8 +91,8 @@
   }
 
   private void verifyEmptyCommand(R8Command command) throws Throwable {
-    assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
-    assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
+    assertEquals(0, ToolHelper.getApp(command).getDexProgramResourcesForTesting().size());
+    assertEquals(0, ToolHelper.getApp(command).getClassProgramResourcesForTesting().size());
     assertFalse(command.useMinification());
     assertFalse(command.useTreeShaking());
     assertEquals(CompilationMode.RELEASE, command.getMode());
@@ -255,7 +299,7 @@
 
   @Test
   public void argumentsInFile() throws Throwable {
-    Path inputFile = temp.newFile("foobar.dex").toPath();
+    Path inputFile = temp.newFile("foobar.class").toPath();
     Path pgConfFile = temp.newFile("pgconf.config").toPath();
     Path argsFile = temp.newFile("more-args.txt").toPath();
     FileUtils.writeTextFile(argsFile, ImmutableList.of(
@@ -267,7 +311,7 @@
     assertEquals(CompilationMode.DEBUG, command.getMode());
     assertFalse(command.useMinification());
     assertFalse(command.useTreeShaking()); // We have no keep rules (proguard config file is empty).
-    assertEquals(1, ToolHelper.getApp(command).getDexProgramResources().size());
+    assertEquals(1, ToolHelper.getApp(command).getClassProgramResourcesForTesting().size());
   }
 
   @Test
@@ -277,9 +321,46 @@
   }
 
   @Test(expected = CompilationFailedException.class)
+  public void dexFileUnsupported() throws Throwable {
+    Path dexFile = temp.newFile("test.dex").toPath();
+    DiagnosticsChecker.checkErrorsContains("DEX input", handler ->
+        R8Command
+            .builder(handler)
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .addProgramFiles(dexFile)
+            .build());
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void dexProviderUnsupported() throws Throwable {
+    Path dexFile = temp.newFile("test.dex").toPath();
+    DiagnosticsChecker.checkErrorsContains("DEX input", handler ->
+        R8.run(R8Command
+            .builder(handler)
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .addProgramResourceProvider(new ProgramResourceProvider() {
+              @Override
+              public Collection<ProgramResource> getProgramResources() throws ResourceException {
+                return Collections.singleton(ProgramResource.fromFile(Kind.DEX, dexFile));
+              }
+            })
+            .build()));
+  }
+
+  @Test
+  public void dexDataUnsupported() throws Throwable {
+    for (Method method : R8Command.Builder.class.getMethods()) {
+      assertNotEquals("addDexProgramData", method.getName());
+    }
+  }
+
+  @Test(expected = CompilationFailedException.class)
   public void vdexFileUnsupported() throws Throwable {
     Path vdexFile = temp.newFile("test.vdex").toPath();
-    D8Command.builder().addProgramFiles(vdexFile).build();
+    R8Command.builder()
+        .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+        .addProgramFiles(vdexFile)
+        .build();
   }
 
   private R8Command parse(String... args) throws CompilationFailedException {
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 33fab08..569beda 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -87,6 +87,7 @@
   private static final String ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR = "tests/2016-12-19/art/lib64";
 
   private static final RuntimeSet LEGACY_RUNTIME = TestCondition.runtimes(
+      DexVm.Version.V4_0_4,
       DexVm.Version.V4_4_4,
       DexVm.Version.V5_1_1,
       DexVm.Version.V6_0_1,
@@ -160,7 +161,7 @@
           .put("800-smali", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Hangs on dalvik.
           .put("802-deoptimization",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           .build();
 
   // Tests that are flaky with the Art version we currently use.
@@ -190,10 +191,10 @@
           // Failed on buildbot with: terminate called after throwing an instance
           // of '__gnu_cxx::recursive_init_error'
           .put("096-array-copy-concurrent-gc",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Sometimes fails with out of memory on Dalvik.
           .put("114-ParallelGC",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Seen crash: currently no more information
           .put("144-static-field-sigquit", TestCondition.any())
           // Opens a lot of file descriptors and depending on the state of the machine this
@@ -521,12 +522,34 @@
           "594-invoke-super",
           "605-new-string-from-bytes",
           "626-const-class-linking"
+      ),
+      DexVm.Version.V4_0_4, ImmutableList.of(
+          // Generally fails on non R8/D8 running.
+          "004-checker-UnsafeTest18",
+          "004-NativeAllocations",
+          "005-annotations",
+          "008-exceptions",
+          "082-inline-execute",
+          "099-vmdebug",
+          "143-string-value",
+          "530-checker-lse2",
+          "536-checker-intrinsic-optimization",
+          "552-invoke-non-existent-super",
+          "580-checker-round",
+          "580-checker-string-fact-intrinsics",
+          "594-invoke-super",
+          "605-new-string-from-bytes",
+          "626-const-class-linking"
       )
   );
 
   // Tests where the R8/D8 output runs in Art but the original does not.
   private static Multimap<String, TestCondition> failingRunWithArtOriginalOnly =
-      new ImmutableListMultimap.Builder<String, TestCondition>().build();
+      new ImmutableListMultimap.Builder<String, TestCondition>()
+          .put("095-switch-MAX_INT",
+              TestCondition.match(
+                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          .build();
 
   // Tests where the output of R8 fails when run with Art.
   private static final Multimap<String, TestCondition> failingRunWithArt =
@@ -535,7 +558,7 @@
           .put("064-field-access",
               TestCondition.match(
                   TestCondition.R8_NOT_AFTER_D8_COMPILER,
-                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
+                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           .put("064-field-access",
               TestCondition.match(
                   TestCondition.R8_COMPILER,
@@ -559,56 +582,56 @@
           // Dalvik fails on reading an uninitialized local.
           .put(
               "471-uninitialized-locals",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Out of memory.
           .put("152-dead-large-object",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Cannot resolve exception handler. Interestingly, D8 generates different code in
           // release mode (which is also the code generated by R8) which passes.
           .put("111-unresolvable-exception",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
+                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           .put("534-checker-bce-deoptimization",
               TestCondition
                   .match(TestCondition.D8_COMPILER, TestCondition.runtimes(DexVm.Version.V6_0_1)))
           // Type not present.
           .put("124-missing-classes",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Failed creating vtable.
           .put("587-inline-class-error",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Failed creating vtable.
           .put("595-error-class",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // NoSuchFieldException: systemThreadGroup on Art 4.4.4.
           .put("129-ThreadGetId",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Verifier says: can't modify final field LMain;.staticFinalField.
           .put("600-verifier-fails",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // VFY: args to if-eq/if-ne must both be refs or cat1.
           .put("134-reg-promotion",
               TestCondition.match(
                   TestCondition.R8_COMPILER,
-                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
+                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           .put("134-reg-promotion",
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE, DexTool.JACK),
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
+                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // VFY: tried to get class from non-ref register.
           .put("506-verify-aput",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // NoSuchMethod: startMethodTracing.
           .put("545-tracing-and-jit",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // filled-new-array arg 0(1) not valid.
           .put("412-new-array",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // TODO(ager): unclear what is failing here.
           .put("098-ddmc",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Unsatisfiable link error:
           // libarttest.so: undefined symbol: _ZN3art6Thread18RunEmptyCheckpointEv
           .put("543-env-long-ref",
@@ -618,7 +641,7 @@
                       .runtimes(DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1)))
           // lib64 libarttest.so: wrong ELF class ELFCLASS64.
           .put("543-env-long-ref",
-              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
+              TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // Regression test for an issue that is not fixed on version 5.1.1. Throws an Exception
           // instance instead of the expected NullPointerException. This bug is only tickled when
           // running the R8 generated code when starting from jar or from dex code generated with
@@ -640,7 +663,23 @@
           // some inlining. The generated code is correct and hence runs on newer versions.
           .put("077-method-override",
               TestCondition.match(TestCondition.compilers(CompilerUnderTest.R8),
-                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
+                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
+          // Dalvik 4.0.4 is missing ReflectiveOperationException class.
+          .put("140-field-packing",
+              TestCondition.match(
+                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          // Dalvik 4.0.4 is missing theUnsafe field.
+          .put("528-long-hint",
+              TestCondition.match(
+                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          // Cannot catch exception in Dalvik 4.0.4.
+          .put("084-class-init",
+              TestCondition.match(
+                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          // Tested regression still exists in Dalvik 4.0.4.
+          .put("301-abstract-protected",
+              TestCondition.match(
+                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
           .build();
 
   // Tests where the output of R8/D8 runs in Art but produces different output than the expected.txt
@@ -651,46 +690,44 @@
           .put("072-precise-gc",
               TestCondition.match(
                   TestCondition.R8_COMPILER,
-                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
+                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           .put("072-precise-gc",
               TestCondition.match(
                   TestCondition.tools(DexTool.JACK, DexTool.NONE),
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
+                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // This one is expected to have different output. It counts instances, but the list that
           // keeps the instances alive is dead and could be garbage collected. The compiler reuses
           // the register for the list and therefore there are no live instances.
           .put("099-vmdebug", TestCondition.any())
           // This test relies on output on stderr, which we currently do not collect.
           .put("143-string-value", TestCondition.any())
-          .put(
-              "800-smali",
+          .put("800-smali",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
                   TestCondition.runtimes(DexVm.Version.V5_1_1, DexVm.Version.V6_0_1)))
           // Triggers regression test in 6.0.1 when using R8/D8 in debug mode.
-          .put(
-              "474-fp-sub-neg",
+          .put("474-fp-sub-neg",
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE, DexTool.JACK),
                   TestCondition.D8_COMPILER,
                   TestCondition.runtimes(DexVm.Version.V6_0_1)))
+          // Produces wrong output
+          .put("015-switch",
+              TestCondition.match(
+                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
           .build();
 
   private static final TestCondition beforeAndroidN =
       TestCondition
           .match(TestCondition
-              .runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1));
+              .runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4, DexVm.Version.V5_1_1,
+                  DexVm.Version.V6_0_1));
   private static final TestCondition beforeAndroidO =
-      TestCondition.match(
-          TestCondition.runtimes(
-              DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1,
-              DexVm.Version.V7_0_0));
+      TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V7_0_0));
+  // TODO(herhut): Change to V8_0_0 once we have a new art VM.
   private static final TestCondition beforeAndroidP =
-      TestCondition.match(
-          TestCondition.runtimes(
-              DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1,
-              DexVm.Version.V7_0_0));
+      TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V7_0_0));
 
   // TODO(ager): Could we test that these fail in the way that we expect?
   private static final Multimap<String, TestCondition> expectedToFailRunWithArt =
@@ -720,7 +757,8 @@
                   TestCondition.tools(DexTool.JACK, DexTool.DX),
                   TestCondition.compilers(CompilerUnderTest.R8, CompilerUnderTest.D8),
                   TestCondition
-                      .runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1)))
+                      .runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4, DexVm.Version.V5_1_1,
+                          DexVm.Version.V6_0_1)))
           // Array index out of bounds exception.
           .put("449-checker-bce", TestCondition.any())
           // Fails: get_vreg_jni.cc:46] Check failed: value == 42u (value=314630384, 42u=42)
@@ -729,8 +767,8 @@
           .put(
               "454-get-vreg",
               TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1,
-                      DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
+                  TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4,
+                      DexVm.Version.V5_1_1, DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
           .put(
               "454-get-vreg",
               TestCondition.match(
@@ -743,8 +781,8 @@
           .put(
               "457-regs",
               TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1,
-                      DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
+                  TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4,
+                      DexVm.Version.V5_1_1, DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
           .put(
               "457-regs",
               TestCondition.match(
@@ -808,12 +846,14 @@
           .put("972-iface-super-multidex",
               TestCondition.match(TestCondition.tools(DexTool.JACK, DexTool.DX),
                   TestCondition
-                      .runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1)))
+                      .runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4, DexVm.Version.V5_1_1,
+                          DexVm.Version.V6_0_1)))
           // Uses dex file version 37 and therefore only runs on Android N and above.
           .put("978-virtual-interface",
               TestCondition.match(TestCondition.tools(DexTool.JACK, DexTool.DX),
                   TestCondition
-                      .runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1)))
+                      .runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4, DexVm.Version.V5_1_1,
+                          DexVm.Version.V6_0_1)))
           .put("979-const-method-handle", beforeAndroidP)
           .build();
 
@@ -857,12 +897,8 @@
           .put("800-smali", TestCondition.match(TestCondition.R8_COMPILER))
           // Contains a loop in the class hierarchy.
           .put("804-class-extends-itself", TestCondition.any())
-          // It is not possible to compute target of method call due to ambiguous methods, thus fail
-          // to generate one dex from several dex inputs that represent an invalid program.
-          .put("004-JniTest", TestCondition.match(TestCondition.R8_COMPILER))
           // These tests have illegal class flag combinations, so we reject them.
           .put("161-final-abstract-class", TestCondition.any())
-          .put("004-JniTest", TestCondition.any())
           .build();
 
   // Tests that does not have dex input for some toolchains.
@@ -1233,7 +1269,7 @@
         // Skip all tests compiled to dex with jack on Dalvik. They have a too high dex
         // version number in the generated output.
         boolean skip = skipTest.contains(name) ||
-            (dexTool == DexTool.JACK && version == DexVm.Version.V4_4_4);
+            (dexTool == DexTool.JACK && version.isOlderThanOrEqual(DexVm.Version.V4_4_4));
         // All the native code for all Art tests is currently linked into the
         // libarttest.so file.
         data.put(
@@ -1360,8 +1396,9 @@
           R8Command.Builder builder =
               R8Command.builder()
                   .setMode(mode)
-                  .setOutput(Paths.get(resultPath), OutputMode.DexIndexed)
-                  .addProgramFiles(ListUtils.map(fileNames, Paths::get));
+                  .setOutput(Paths.get(resultPath), OutputMode.DexIndexed);
+          // Add program files directly to the underlying app to avoid errors on DEX inputs.
+          ToolHelper.getAppBuilder(builder).addProgramFiles(ListUtils.map(fileNames, Paths::get));
           Integer minSdkVersion = needMinSdkVersion.get(name);
           if (minSdkVersion != null) {
             builder.setMinApiLevel(minSdkVersion);
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index cf1c6d7..57ae420 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -20,22 +20,31 @@
 public class R8RunExamplesAndroidOTest extends RunExamplesAndroidOTest<R8Command.Builder> {
 
   private static Map<DexVm.Version, List<String>> alsoFailsOn =
-      ImmutableMap.of(
-          Version.V4_4_4, ImmutableList.of(
+      ImmutableMap.<DexVm.Version, List<String>>builder()
+          .put(Version.V4_0_4,
+              ImmutableList.of(
               "invokecustom-with-shrinking"
-          ),
-          Version.V5_1_1, ImmutableList.of(
+              ))
+          .put(Version.V4_4_4,
+              ImmutableList.of(
               "invokecustom-with-shrinking"
-          ),
-          Version.V6_0_1, ImmutableList.of(
+              ))
+          .put(Version.V5_1_1,
+              ImmutableList.of(
               "invokecustom-with-shrinking"
-          ),
-          Version.V7_0_0, ImmutableList.of(
+              ))
+          .put(Version.V6_0_1,
+              ImmutableList.of(
               "invokecustom-with-shrinking"
-          ),
-          Version.DEFAULT, ImmutableList.of(
-          )
-      );
+              ))
+          .put(Version.V7_0_0,
+              ImmutableList.of(
+                  "invokecustom-with-shrinking"
+              ))
+          .put(Version.DEFAULT,
+              ImmutableList.of(
+              ))
+          .build();
 
   @Test
   public void invokeCustomWithShrinking() throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
index 39c3cd4..b063770 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -19,22 +19,31 @@
 public class R8RunExamplesAndroidPTest extends RunExamplesAndroidPTest<R8Command.Builder> {
 
   private static Map<DexVm.Version, List<String>> alsoFailsOn =
-      ImmutableMap.of(
-          DexVm.Version.V4_4_4, ImmutableList.of(
-              "invokecustom-with-shrinking"
-          ),
-          DexVm.Version.V5_1_1, ImmutableList.of(
-              "invokecustom-with-shrinking"
-          ),
-          DexVm.Version.V6_0_1, ImmutableList.of(
-              "invokecustom-with-shrinking"
-          ),
-          DexVm.Version.V7_0_0, ImmutableList.of(
-              "invokecustom-with-shrinking"
-          ),
-          DexVm.Version.DEFAULT, ImmutableList.of(
-          )
-      );
+      ImmutableMap.<DexVm.Version, List<String>>builder()
+          .put(DexVm.Version.V4_0_4,
+              ImmutableList.of(
+                  "invokecustom-with-shrinking"
+              ))
+          .put(DexVm.Version.V4_4_4,
+              ImmutableList.of(
+                  "invokecustom-with-shrinking"
+              ))
+          .put(DexVm.Version.V5_1_1,
+              ImmutableList.of(
+                  "invokecustom-with-shrinking"
+              ))
+          .put(DexVm.Version.V6_0_1,
+              ImmutableList.of(
+                  "invokecustom-with-shrinking"
+              ))
+          .put(DexVm.Version.V7_0_0,
+              ImmutableList.of(
+                  "invokecustom-with-shrinking"
+              ))
+          .put(DexVm.Version.DEFAULT,
+              ImmutableList.of(
+              ))
+          .build();
 
   @Test
   public void invokeCustomWithShrinking() throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 9d275d4..8e23386 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.OutputMode;
 import java.io.IOException;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Map;
@@ -92,6 +93,16 @@
     }
   }
 
+  private R8Command.Builder addInputFile(R8Command.Builder builder) throws NoSuchFileException {
+    if (input == Input.DX) {
+      // If input is DEX code, use the tool helper to add the DEX sources as R8 disallows them.
+      ToolHelper.getAppBuilder(builder).addProgramFiles(getInputFile());
+    } else {
+      builder.addProgramFiles(getInputFile());
+    }
+    return builder;
+  }
+
   public Path getOriginalJarFile(String postFix) {
     return Paths.get(getExampleDir(), pkg + postFix + JAR_EXTENSION);
   }
@@ -116,17 +127,17 @@
     switch (compiler) {
       case D8: {
         assertTrue(output == Output.DEX);
-        ToolHelper.runD8(D8Command.builder()
-            .addProgramFiles(getInputFile())
-            .setOutput(getOutputFile(), outputMode)
-            .setMode(mode)
-            .build());
+        ToolHelper.runD8(
+            D8Command.builder()
+                .addProgramFiles(getInputFile())
+                .setOutput(getOutputFile(), outputMode)
+                .setMode(mode)
+                .build());
         break;
       }
       case R8: {
         ToolHelper.runR8(
-            R8Command.builder()
-                .addProgramFiles(getInputFile())
+            addInputFile(R8Command.builder())
                 .setOutput(getOutputFile(), outputMode)
                 .setMode(mode)
                 .build(),
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 5416e95..f4783b2 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -156,15 +156,12 @@
         // being true even when the converse is the case when running on the JVM.
         .put("enclosingmethod.Main", TestCondition.any())
         // Early art versions incorrectly print Float.MIN_VALUE.
-        .put(
-            "filledarray.FilledArray",
-            TestCondition.match(
-                TestCondition.runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
+        .put("filledarray.FilledArray",
+            TestCondition.match(TestCondition.runtimesUpTo(Version.V6_0_1)))
         // Early art versions incorrectly print doubles.
         .put(
             "regress_70736958.Test",
-            TestCondition.match(
-                TestCondition.runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
+            TestCondition.match(TestCondition.runtimesUpTo(Version.V6_0_1)))
         .build();
   }
 
@@ -173,10 +170,11 @@
   protected Map<String, TestCondition> getSkip() {
     return new ImmutableMap.Builder<String, TestCondition>()
         // Test uses runtime methods which are not available on older Art versions.
-        .put(
-            "regress_70703087.Test",
-            TestCondition.match(
-                TestCondition.runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
+        .put("regress_70703087.Test",
+            TestCondition.match(TestCondition.runtimesUpTo(Version.V6_0_1)))
+        // Test uses runtime methods which are not available on older Art versions.
+        .put("loop.UdpServer",
+            TestCondition.match(TestCondition.runtimesUpTo(Version.V4_0_4)))
         .build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index c80acd5..126bccf 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -44,6 +44,11 @@
           // Smali code contains an empty switch payload.
           "sparse-switch",
           "regression/33846227"
+      ),
+      Version.V4_0_4, ImmutableList.of(
+          // Smali code contains an empty switch payload.
+          "sparse-switch",
+          "regression/33846227"
       )
   );
 
@@ -55,6 +60,12 @@
               "type-confusion-regression2", "java.lang.NullPointerException\n",
               "type-confusion-regression3", "java.lang.NullPointerException\n",
               "merge-blocks-regression", "java.lang.NullPointerException\n"
+          ),
+          Version.V4_0_4, ImmutableMap.of(
+              "bad-codegen", "java.lang.NullPointerException\n",
+              "type-confusion-regression2", "java.lang.NullPointerException\n",
+              "type-confusion-regression3", "java.lang.NullPointerException\n",
+              "merge-blocks-regression", "java.lang.NullPointerException\n"
           )
       );
 
@@ -65,6 +76,11 @@
           // The invokes are in fact invalid, but the test expects the current Art behavior
           // of throwing an IncompatibleClassChange exception. Dalvik fails to verify.
           "illegal-invokes"
+      ),
+      Version.V4_0_4, ImmutableList.of(
+          // The invokes are in fact invalid, but the test expects the current Art behavior
+          // of throwing an IncompatibleClassChange exception. Dalvik fails to verify.
+          "illegal-invokes"
       )
   );
 
@@ -138,12 +154,11 @@
   public void SmaliTest() throws Exception {
     Path originalDexFile = Paths.get(SMALI_DIR, directoryName, dexFileName);
     String outputPath = temp.getRoot().getCanonicalPath();
-    R8.run(
-        R8Command.builder()
-            .addProgramFiles(originalDexFile)
+    R8Command.Builder builder = R8Command.builder()
             .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
-            .setOutput(Paths.get(outputPath), OutputMode.DexIndexed)
-            .build());
+            .setOutput(Paths.get(outputPath), OutputMode.DexIndexed);
+    ToolHelper.getAppBuilder(builder).addProgramFiles(originalDexFile);
+    R8.run(builder.build());
 
     if (!ToolHelper.artSupported()) {
       return;
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 0e7d742..cfa2596 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -180,63 +180,88 @@
       ImmutableList.of(
           "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
 
-  private static Map<DexVm.Version, List<String>> failsOn =
-      ImmutableMap.of(
-          DexVm.Version.V4_4_4, ImmutableList.of(
-              // API not supported
-              "paramnames",
-              "repeat_annotations_new_api",
-              // Dex version not supported
-              "invokepolymorphic",
-              "invokecustom",
-              "invokecustom2",
-              "DefaultMethodInAndroidJar25",
-              "StaticMethodInAndroidJar25",
-              "testMissingInterfaceDesugared2AndroidO",
-              "testCallToMissingSuperInterfaceDesugaredAndroidO",
-              "testMissingSuperDesugaredAndroidO"
-          ),
-          DexVm.Version.V5_1_1, ImmutableList.of(
-              // API not supported
-              "paramnames",
-              "repeat_annotations_new_api",
-              // Dex version not supported
-              "invokepolymorphic",
-              "invokecustom",
-              "invokecustom2",
-              "DefaultMethodInAndroidJar25",
-              "StaticMethodInAndroidJar25",
-              "testMissingInterfaceDesugared2AndroidO",
-              "testCallToMissingSuperInterfaceDesugaredAndroidO",
-              "testMissingSuperDesugaredAndroidO"
-          ),
-          DexVm.Version.V6_0_1, ImmutableList.of(
-              // API not supported
-              "paramnames",
-              "repeat_annotations_new_api",
-              // Dex version not supported
-              "invokepolymorphic",
-              "invokecustom",
-              "invokecustom2",
-              "DefaultMethodInAndroidJar25",
-              "StaticMethodInAndroidJar25",
-              "testMissingInterfaceDesugared2AndroidO",
-              "testCallToMissingSuperInterfaceDesugaredAndroidO",
-              "testMissingSuperDesugaredAndroidO"
-          ),
-          DexVm.Version.V7_0_0, ImmutableList.of(
-              // API not supported
-              "paramnames",
-              // Dex version not supported
-              "invokepolymorphic",
-              "invokecustom",
-              "invokecustom2",
-              "testMissingInterfaceDesugared2AndroidO",
-              "testCallToMissingSuperInterfaceDesugaredAndroidO",
-              "testMissingSuperDesugaredAndroidO"
-          ),
-          DexVm.Version.DEFAULT, ImmutableList.of()
-      );
+  private static Map<DexVm.Version, List<String>> failsOn;
+
+  static {
+    ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
+    builder
+        .put(
+            DexVm.Version.V4_0_4, ImmutableList.of(
+                // API not supported
+                "paramnames",
+                "repeat_annotations_new_api",
+                // Dex version not supported
+                "invokepolymorphic",
+                "invokecustom",
+                "invokecustom2",
+                "DefaultMethodInAndroidJar25",
+                "StaticMethodInAndroidJar25",
+                "testMissingInterfaceDesugared2AndroidO",
+                "testCallToMissingSuperInterfaceDesugaredAndroidO",
+                "testMissingSuperDesugaredAndroidO"
+            ))
+        .put(
+            DexVm.Version.V4_4_4, ImmutableList.of(
+                // API not supported
+                "paramnames",
+                "repeat_annotations_new_api",
+                // Dex version not supported
+                "invokepolymorphic",
+                "invokecustom",
+                "invokecustom2",
+                "DefaultMethodInAndroidJar25",
+                "StaticMethodInAndroidJar25",
+                "testMissingInterfaceDesugared2AndroidO",
+                "testCallToMissingSuperInterfaceDesugaredAndroidO",
+                "testMissingSuperDesugaredAndroidO"
+            ))
+        .put(
+            DexVm.Version.V5_1_1, ImmutableList.of(
+                // API not supported
+                "paramnames",
+                "repeat_annotations_new_api",
+                // Dex version not supported
+                "invokepolymorphic",
+                "invokecustom",
+                "invokecustom2",
+                "DefaultMethodInAndroidJar25",
+                "StaticMethodInAndroidJar25",
+                "testMissingInterfaceDesugared2AndroidO",
+                "testCallToMissingSuperInterfaceDesugaredAndroidO",
+                "testMissingSuperDesugaredAndroidO"
+            ))
+        .put(
+            DexVm.Version.V6_0_1, ImmutableList.of(
+                // API not supported
+                "paramnames",
+                "repeat_annotations_new_api",
+                // Dex version not supported
+                "invokepolymorphic",
+                "invokecustom",
+                "invokecustom2",
+                "DefaultMethodInAndroidJar25",
+                "StaticMethodInAndroidJar25",
+                "testMissingInterfaceDesugared2AndroidO",
+                "testCallToMissingSuperInterfaceDesugaredAndroidO",
+                "testMissingSuperDesugaredAndroidO"
+            ))
+        .put(
+            DexVm.Version.V7_0_0, ImmutableList.of(
+                // API not supported
+                "paramnames",
+                // Dex version not supported
+                "invokepolymorphic",
+                "invokecustom",
+                "invokecustom2",
+                "testMissingInterfaceDesugared2AndroidO",
+                "testCallToMissingSuperInterfaceDesugaredAndroidO",
+                "testMissingSuperDesugaredAndroidO"
+            ))
+        .put(
+            DexVm.Version.DEFAULT, ImmutableList.of()
+        );
+    failsOn = builder.build();
+  }
 
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index c765f77..bc898bd 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -173,25 +173,19 @@
           "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
 
   private static Map<DexVm.Version, List<String>> failsOn =
-      ImmutableMap.of(
-          DexVm.Version.V4_4_4, ImmutableList.of(
-              // Dex version not supported
-              "invokecustom"
-          ),
-          DexVm.Version.V5_1_1, ImmutableList.of(
-              // Dex version not supported
-              "invokecustom"
-          ),
-          DexVm.Version.V6_0_1, ImmutableList.of(
-              // Dex version not supported
-              "invokecustom"
-          ),
-          DexVm.Version.V7_0_0, ImmutableList.of(
-              // Dex version not supported
-              "invokecustom"
-          ),
-          DexVm.Version.DEFAULT, ImmutableList.of()
-      );
+      ImmutableMap.<DexVm.Version, List<String>>builder()
+          // Dex version not supported
+          .put(DexVm.Version.V4_0_4, ImmutableList.of("invokecustom"))
+          // Dex version not supported
+          .put(DexVm.Version.V4_4_4, ImmutableList.of("invokecustom"))
+          // Dex version not supported
+          .put(DexVm.Version.V5_1_1, ImmutableList.of("invokecustom"))
+          // Dex version not supported
+          .put(DexVm.Version.V6_0_1, ImmutableList.of("invokecustom"))
+          // Dex version not supported
+          .put(DexVm.Version.V7_0_0, ImmutableList.of("invokecustom"))
+          .put(DexVm.Version.DEFAULT, ImmutableList.of())
+          .build();
 
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index fb44d6f..281b0da 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -171,32 +171,37 @@
   private static List<String> minSdkErrorExpected =
       ImmutableList.of("varhandle-error-due-to-min-sdk");
 
-  private static Map<DexVm.Version, List<String>> failsOn =
-      ImmutableMap.of(
-          DexVm.Version.V4_4_4, ImmutableList.of(
-              "native-private-interface-methods",
-              // Dex version not supported
-              "varhandle"
-          ),
-          DexVm.Version.V5_1_1, ImmutableList.of(
-              "native-private-interface-methods",
-              // Dex version not supported
-              "varhandle"
-          ),
-          DexVm.Version.V6_0_1, ImmutableList.of(
-              "native-private-interface-methods",
-              // Dex version not supported
-              "varhandle"
-          ),
-          DexVm.Version.V7_0_0, ImmutableList.of(
-              // Dex version not supported
-              "varhandle"
-          ),
-          DexVm.Version.DEFAULT, ImmutableList.of(
-              // TODO(mikaelpeltier): Update runtime when the support will be ready
-              "varhandle"
-          )
-      );
+  private static Map<DexVm.Version, List<String>> failsOn;
+
+  static {
+    ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
+    builder
+        .put(DexVm.Version.V4_0_4, ImmutableList.of(
+            "native-private-interface-methods",// Dex version not supported
+            "varhandle"
+        ))
+        .put(DexVm.Version.V4_4_4, ImmutableList.of(
+            "native-private-interface-methods",// Dex version not supported
+            "varhandle"
+        ))
+        .put(DexVm.Version.V5_1_1, ImmutableList.of(
+            "native-private-interface-methods",// Dex version not supported
+            "varhandle"
+        ))
+        .put(DexVm.Version.V6_0_1, ImmutableList.of("native-private-interface-methods",
+            // Dex version not supported
+            "varhandle"
+        ))
+        .put(DexVm.Version.V7_0_0, ImmutableList.of(
+            // Dex version not supported
+            "varhandle"
+        ))
+        .put(DexVm.Version.DEFAULT, ImmutableList.of(
+            // TODO(mikaelpeltier): Update runtime when the support will be ready
+            "varhandle"
+        ));
+    failsOn = builder.build();
+  }
 
   // Defines methods failing on JVM, specifies the output to be used for comparison.
   private static Map<String, String> expectedJvmResult =
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 4412dec..fd238f3 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -277,18 +277,18 @@
   }
 
   /**
-   * Generate a Proguard configuration for keeping the "public static void main(String[])" method
-   * of the specified class.
+   * Generate a Proguard configuration for keeping the "public static void main(String[])" method of
+   * the specified class.
    */
-  public String keepMainProguardConfiguration(Class clazz) {
+  public static String keepMainProguardConfiguration(Class clazz) {
     return keepMainProguardConfiguration(clazz.getCanonicalName());
   }
 
   /**
-   * Generate a Proguard configuration for keeping the "public static void main(String[])" method
-   * of the specified class.
+   * Generate a Proguard configuration for keeping the "public static void main(String[])" method of
+   * the specified class.
    */
-  public String keepMainProguardConfiguration(String clazz) {
+  public static String keepMainProguardConfiguration(String clazz) {
     return "-keep public class " + clazz + " {\n"
         + "  public static void main(java.lang.String[]);\n"
         + "}\n"
@@ -296,11 +296,11 @@
   }
 
   /**
-   * Generate a Proguard configuration for keeping the "public static void main(String[])" method
-   * of the specified class and specify if -allowaccessmodification and -dontobfuscate are added
-   * as well.
+   * Generate a Proguard configuration for keeping the "public static void main(String[])" method of
+   * the specified class and specify if -allowaccessmodification and -dontobfuscate are added as
+   * well.
    */
-  public String keepMainProguardConfiguration(
+  public static String keepMainProguardConfiguration(
       Class clazz, boolean allowaccessmodification, boolean obfuscate) {
     return keepMainProguardConfiguration(clazz)
         + (allowaccessmodification ? "-allowaccessmodification\n" : "")
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index 1aca2bd..4babe9b 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -99,6 +99,10 @@
     return new RuntimeSet(EnumSet.copyOf(Arrays.asList(runtimes)));
   }
 
+  public static RuntimeSet runtimesUpTo(DexVm.Version upto) {
+    return new RuntimeSet(EnumSet.range(DexVm.Version.first(), upto));
+  }
+
   public static TestCondition match(
       ToolSet tools,
       CompilerSet compilers,
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index cf5e451..b487308 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -11,8 +11,11 @@
 import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.DexOverflowException;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -52,6 +55,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import joptsimple.internal.Strings;
@@ -86,6 +90,8 @@
   private static final String PROGUARD = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
 
   public enum DexVm {
+    ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
+    ART_4_0_4_HOST(Version.V4_0_4, Kind.HOST),
     ART_4_4_4_TARGET(Version.V4_4_4, Kind.TARGET),
     ART_4_4_4_HOST(Version.V4_4_4, Kind.HOST),
     ART_5_1_1_TARGET(Version.V5_1_1, Kind.TARGET),
@@ -104,6 +110,7 @@
             .build();
 
     public enum Version {
+      V4_0_4("4.0.4"),
       V4_4_4("4.4.4"),
       V5_1_1("5.1.1"),
       V6_0_1("6.0.1"),
@@ -131,6 +138,15 @@
       }
 
       private String shortName;
+
+      public static Version first() {
+        return V4_0_4;
+      }
+
+      static {
+        // Ensure first is always first.
+        assert Arrays.stream(values()).allMatch(v -> v == first() || v.compareTo(first()) > 0);
+      }
     }
 
     public enum Kind {
@@ -371,14 +387,16 @@
           .put(DexVm.ART_7_0_0_HOST, "art-7.0.0")
           .put(DexVm.ART_6_0_1_HOST, "art-6.0.1")
           .put(DexVm.ART_5_1_1_HOST, "art-5.1.1")
-          .put(DexVm.ART_4_4_4_HOST, "dalvik").build();
+          .put(DexVm.ART_4_4_4_HOST, "dalvik")
+          .put(DexVm.ART_4_0_4_HOST, "dalvik-4.0.4").build();
   private static final Map<DexVm, String> ART_BINARY_VERSIONS =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "bin/art")
           .put(DexVm.ART_7_0_0_HOST, "bin/art")
           .put(DexVm.ART_6_0_1_HOST, "bin/art")
           .put(DexVm.ART_5_1_1_HOST, "bin/art")
-          .put(DexVm.ART_4_4_4_HOST, "bin/dalvik").build();
+          .put(DexVm.ART_4_4_4_HOST, "bin/dalvik")
+          .put(DexVm.ART_4_0_4_HOST, "bin/dalvik").build();
 
   private static final Map<DexVm, String> ART_BINARY_VERSIONS_X64 =
       ImmutableMap.of(
@@ -398,13 +416,19 @@
           "core-oj-hostdex.jar",
           "apache-xml-hostdex.jar");
 
-  private static final Map<DexVm, List<String>> BOOT_LIBS =
-      ImmutableMap.of(
-          DexVm.ART_DEFAULT, ART_BOOT_LIBS,
-          DexVm.ART_7_0_0_HOST, ART_BOOT_LIBS,
-          DexVm.ART_6_0_1_HOST, ART_BOOT_LIBS,
-          DexVm.ART_5_1_1_HOST, ART_BOOT_LIBS,
-          DexVm.ART_4_4_4_HOST, DALVIK_BOOT_LIBS);
+  private static final Map<DexVm, List<String>> BOOT_LIBS;
+
+  static {
+    ImmutableMap.Builder<DexVm, List<String>> builder = ImmutableMap.builder();
+    builder
+        .put(DexVm.ART_DEFAULT, ART_BOOT_LIBS)
+        .put(DexVm.ART_7_0_0_HOST, ART_BOOT_LIBS)
+        .put(DexVm.ART_6_0_1_HOST, ART_BOOT_LIBS)
+        .put(DexVm.ART_5_1_1_HOST, ART_BOOT_LIBS)
+        .put(DexVm.ART_4_4_4_HOST, DALVIK_BOOT_LIBS)
+        .put(DexVm.ART_4_0_4_HOST, DALVIK_BOOT_LIBS);
+    BOOT_LIBS = builder.build();
+  }
 
   private static final String LIB_PATH = TOOLS + "/linux/art/lib";
   private static final String DX = getDxExecutablePath();
@@ -459,9 +483,12 @@
       // TODO(mikaelpeltier) Android P does not yet have his android.jar use the O version
       minSdkVersion = AndroidApiLevel.O.getLevel();
     }
-    return String.format(
+    String jar = String.format(
         ANDROID_JAR_PATTERN,
         minSdkVersion == AndroidApiLevel.getDefault().getLevel() ? DEFAULT_MIN_SDK : minSdkVersion);
+    assert Files.exists(Paths.get(jar))
+        : "Expected android jar to exist for API level " + minSdkVersion;
+    return jar;
   }
 
   public static Path getJdwpTestsCfJarPath(int minSdk) {
@@ -557,8 +584,16 @@
         return AndroidApiLevel.O.getLevel();
       case V7_0_0:
         return AndroidApiLevel.N.getLevel();
+      case V6_0_1:
+        return AndroidApiLevel.M.getLevel();
+      case V5_1_1:
+        return AndroidApiLevel.L_MR1.getLevel();
+      case V4_4_4:
+        return AndroidApiLevel.K.getLevel();
+      case V4_0_4:
+        return AndroidApiLevel.I_MR1.getLevel();
       default:
-        return AndroidApiLevel.getDefault().getLevel();
+        throw new Unreachable("Missing min api level for dex vm " + dexVm);
     }
   }
 
@@ -1095,10 +1130,20 @@
     return new ProcessResult(p.exitValue(), stdoutReader.getResult(), stderrReader.getResult());
   }
 
+  public static R8Command.Builder addProguardConfigurationConsumer(
+      R8Command.Builder builder, Consumer<ProguardConfiguration.Builder> consumer) {
+    builder.addProguardConfigurationConsumerForTesting(consumer);
+    return builder;
+  }
+
   public static AndroidApp getApp(BaseCommand command) {
     return command.getInputApp();
   }
 
+  public static AndroidApp.Builder getAppBuilder(BaseCommand.Builder builder) {
+    return builder.getAppBuilder();
+  }
+
   public static AndroidApp.Builder builderFromProgramDirectory(Path directory) throws IOException {
     AndroidApp.Builder builder = AndroidApp.builder();
     Files.walkFileTree(
@@ -1115,4 +1160,16 @@
         });
     return builder;
   }
+
+  public static void writeApplication(DexApplication application, InternalOptions options)
+      throws ExecutionException, DexOverflowException {
+    R8.writeApplication(
+        Executors.newSingleThreadExecutor(),
+        application,
+        null,
+        NamingLens.getIdentityLens(),
+        null,
+        options,
+        null);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
index 7438498..825b434 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
@@ -17,12 +17,11 @@
   DexInspector runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations)
       throws Exception {
     Path dexOutputDir = temp.newFolder().toPath();
-    R8Command command =
+    R8Command.Builder commandBuilder =
         new CompatProguardCommandBuilder(true)
-            .addDexProgramData(builder.compile(), Origin.unknown())
             .setOutput(dexOutputDir, OutputMode.DexIndexed)
-            .addProguardConfiguration(proguardConfigurations, Origin.unknown())
-            .build();
-    return new DexInspector(ToolHelper.runR8(command));
+            .addProguardConfiguration(proguardConfigurations, Origin.unknown());
+    ToolHelper.getAppBuilder(commandBuilder).addDexProgramData(builder.compile(), Origin.unknown());
+    return new DexInspector(ToolHelper.runR8(commandBuilder.build()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 0878a30..29c9871 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -799,7 +799,8 @@
             command.perform(this);
           } catch (TestErrorException e) {
             boolean ignoreException = false;
-            if (config.isDexRuntime() && ToolHelper.getDexVm().getVersion() == Version.V4_4_4) {
+            if (config.isDexRuntime()
+                && ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
               // Dalvik has flaky synchronization issue on shutdown. The workaround is to ignore
               // the exception if and only if we know that it's the final resume command.
               if (debuggeeState == null && commandsQueue.isEmpty()) {
diff --git a/src/test/java/com/android/tools/r8/dexmerger/DexFileMergerTests.java b/src/test/java/com/android/tools/r8/dexmerger/DexFileMergerTests.java
index 44d3948..9dfc37b 100644
--- a/src/test/java/com/android/tools/r8/dexmerger/DexFileMergerTests.java
+++ b/src/test/java/com/android/tools/r8/dexmerger/DexFileMergerTests.java
@@ -72,7 +72,7 @@
             AndroidApiLevel.N.getLevel(),
             MAX_METHOD_COUNT / 2 + 1 + extraMethodCount);
     Path appDir = temp.newFolder().toPath().resolve("merger-input.zip");
-    assertEquals(programResourcesSize, generatedApp.getDexProgramResources().size());
+    assertEquals(programResourcesSize, generatedApp.getDexProgramResourcesForTesting().size());
     generatedApp.write(appDir, OutputMode.DexIndexed);
 
     Path outZip = temp.getRoot().toPath().resolve("out.zip");
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index 92897fa..3cab10a 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -74,7 +74,7 @@
     assertNull(appInfo.lookupDirectTarget(method.method));
     assertNotNull(appInfo.lookupStaticTarget(method.method));
 
-    if (ToolHelper.getDexVm().getVersion() == DexVm.Version.V4_4_4) {
+    if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(DexVm.Version.V4_4_4)) {
       // Dalvik rejects at verification time instead of producing the
       // expected IncompatibleClassChangeError.
       try {
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index d4745b4..672973f 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -93,10 +93,12 @@
       builder.setMode(mode);
       builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
       builder.setMinApiLevel(AndroidApiLevel.L.getLevel());
-      builder.addProguardConfigurationConsumer(b -> {
-        b.setPrintSeeds(false);
-        b.setIgnoreWarnings(true);
-      });
+      ToolHelper.addProguardConfigurationConsumer(
+          builder,
+          pgConfig -> {
+            pgConfig.setPrintSeeds(false);
+            pgConfig.setIgnoreWarnings(true);
+          });
       outputApp = ToolHelper.runR8(builder.build(), optionsConsumer);
     } else {
       assert compiler == CompilerUnderTest.D8;
@@ -150,7 +152,7 @@
   public int applicationSize(AndroidApp app) throws IOException {
     int bytes = 0;
     try (Closer closer = Closer.create()) {
-      for (Resource dex : app.getDexProgramResources()) {
+      for (Resource dex : app.getDexProgramResourcesForTesting()) {
         bytes += ByteStreams.toByteArray(closer.register(dex.getStream())).length;
       }
     }
@@ -168,8 +170,8 @@
         app1.writeToDirectory(temp.newFolder("app1").toPath(), OutputMode.Indexed);
         app2.writeToDirectory(temp.newFolder("app2").toPath(), OutputMode.Indexed);
       }
-      List<ProgramResource> files1 = app1.getDexProgramResources();
-      List<ProgramResource> files2 = app2.getDexProgramResources();
+      List<ProgramResource> files1 = app1.getDexProgramResourcesForTesting();
+      List<ProgramResource> files2 = app2.getDexProgramResourcesForTesting();
       assertEquals(files1.size(), files2.size());
       for (int index = 0; index < files1.size(); index++) {
         InputStream file1 = closer.register(files1.get(index).getStream());
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
index 71868d6..f8aa934 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
@@ -30,7 +30,7 @@
         ImmutableList.of());
     int bytes = 0;
     try (Closer closer = Closer.create()) {
-      for (Resource dex : app.getDexProgramResources()) {
+      for (Resource dex : app.getDexProgramResourcesForTesting()) {
         bytes += ByteStreams.toByteArray(closer.register(dex.getStream())).length;
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 0e97b13..074c704 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir;
 
-import com.android.tools.r8.R8;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.AppInfo;
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
@@ -29,7 +28,6 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
 import org.antlr.runtime.RecognitionException;
 
 public class IrInjectionTestBase extends SmaliTestBase {
@@ -123,14 +121,7 @@
     private AndroidApp writeDex(DexApplication application, InternalOptions options)
         throws DexOverflowException {
       try {
-        R8.writeApplication(
-            Executors.newSingleThreadExecutor(),
-            application,
-            null,
-            NamingLens.getIdentityLens(),
-            null,
-            options,
-            null);
+        ToolHelper.writeApplication(application, options);
         options.signalFinishedToProgramConsumer();
         return consumers.build();
       } catch (ExecutionException e) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8StaticInlining.java b/src/test/java/com/android/tools/r8/ir/optimize/R8StaticInlining.java
new file mode 100644
index 0000000..5102878
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8StaticInlining.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2018, 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.ir.optimize;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.OutputMode;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This test verifies that semantic of class initialization is preserved when a static method
+ * invocation is inlined.
+ */
+@RunWith(Parameterized.class)
+public class R8StaticInlining extends TestBase {
+
+  // TODO(shertz) add CF output
+  @Parameters(name = "{0}_{1}")
+  public static Collection<String[]> data() {
+    return ImmutableList.of(
+        new String[]{"staticinlining", "staticinlining.Main"}
+    );
+  }
+
+  private final String folder;
+  private final String mainClass;
+
+  public R8StaticInlining(String folder, String mainClass) {
+    this.folder = folder;
+    this.mainClass = mainClass;
+  }
+
+  @Test
+  @Ignore("b/71524812")
+  public void testInliningOfStatic() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+
+    Path proguardRules = Paths.get(ToolHelper.EXAMPLES_DIR, folder, "keep-rules.txt");
+    Path jarFile =
+        Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, folder + FileUtils.JAR_EXTENSION);
+
+    // Build with R8
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder.addProgramFiles(jarFile);
+    AndroidApp app = compileWithR8(builder.build(), proguardRules);
+
+    // Compare original and generated DEX files.
+    String originalDexFile = Paths
+        .get(ToolHelper.EXAMPLES_BUILD_DIR, folder, ToolHelper.DEFAULT_DEX_FILENAME).toString();
+    Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
+    app.writeToZip(generatedDexFile, OutputMode.DexIndexed);
+    String artOutput = ToolHelper
+        .checkArtOutputIdentical(originalDexFile, generatedDexFile.toString(), mainClass,
+            ToolHelper.getDexVm());
+
+    // Compare with Java.
+    ToolHelper.ProcessResult javaResult = ToolHelper.runJava(jarFile, mainClass);
+    if (javaResult.exitCode != 0) {
+      System.out.println(javaResult.stdout);
+      System.err.println(javaResult.stderr);
+      fail("JVM failed for: " + mainClass);
+    }
+    assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
index a38ee9e..ddf3dbb 100644
--- a/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
+++ b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
@@ -31,7 +31,7 @@
 
   private void runTest(JasminBuilder builder, String main) throws Exception {
     String javaResult = runOnJava(builder, main);
-    if (ToolHelper.getDexVm().getVersion() == DexVm.Version.V4_4_4) {
+    if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(DexVm.Version.V4_4_4)) {
       // On dalvik the need for truncation is treated as a verification error.
       runOnDalvikCheckVerifyError(builder, main);
       runOnDalvikDxCheckVerifyError(builder, main);
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index cefa888..8e2242d 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8Command;
-import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexAnnotationElement;
@@ -60,7 +59,7 @@
 
   private AndroidApp compileWithR8(Path inputPath, Path outputPath, Path keepRulesPath)
       throws IOException, CompilationException, CompilationFailedException {
-    return R8.runInternal(
+    return ToolHelper.runR8(
         R8Command.builder()
             .addProgramFiles(inputPath)
             .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index aecdf33..122db11 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -431,7 +431,7 @@
         MANY_CLASSES, AndroidApiLevel.K.getLevel(), true, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
     generated.write(getManyClassesForceMultiDexAppPath(), OutputMode.Indexed);
     // Make sure the generated app indeed has multiple dex files.
-    assertTrue(generated.getDexProgramResources().size() > 1);
+    assertTrue(generated.getDexProgramResourcesForTesting().size() > 1);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index db18703..71ad0ac 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -80,14 +80,12 @@
     String out = temp.getRoot().getCanonicalPath();
     // NOTE: It is important to turn off inlining to ensure
     // dex inspection of invokes is predictable.
-    ToolHelper.runR8(
-        R8Command.builder()
-            .setOutput(Paths.get(out), OutputMode.DexIndexed)
-            .addProgramFiles(programFile)
-            .addLibraryFiles(JAR_LIBRARIES)
-            .setMinApiLevel(minApiLevel)
-            .build(),
-        options -> options.inlineAccessors = false);
+    R8Command.Builder builder = R8Command.builder()
+        .setOutput(Paths.get(out), OutputMode.DexIndexed)
+        .addLibraryFiles(JAR_LIBRARIES)
+        .setMinApiLevel(minApiLevel);
+    ToolHelper.getAppBuilder(builder).addProgramFiles(programFile);
+    ToolHelper.runR8(builder.build(), options -> options.inlineAccessors = false);
   }
 
   private static boolean coolInvokes(InstructionSubject instruction) {
diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
index 070b6da..4c9bbd1 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -76,12 +76,12 @@
     }
     ProguardMapConsumer mapConsumer = new ProguardMapConsumer();
     runR8(
-        getCommandForApps(out, flagForObfuscation, NAMING044_JAR)
-            .setProguardMapConsumer(mapConsumer)
-            .addProguardConfigurationConsumer(
-                c -> {
-                  c.setPrintMapping(true);
-                  c.setPrintMappingFile(proguardMap);
+        ToolHelper.addProguardConfigurationConsumer(
+                getCommandForApps(out, flagForObfuscation, NAMING044_JAR)
+                    .setProguardMapConsumer(mapConsumer),
+                pgConfig -> {
+                  pgConfig.setPrintMapping(true);
+                  pgConfig.setPrintMappingFile(proguardMap);
                 })
             .build());
 
@@ -92,13 +92,13 @@
 
     Path instrOut = temp.newFolder("instr").toPath();
     Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "applymapping044", "keep-rules.txt");
-    AndroidApp instrApp = runR8(
-        getCommandForInstrumentation(instrOut, flag, NAMING044_JAR, APPLYMAPPING044_JAR)
-            .addProguardConfigurationConsumer(c -> {
-              c.setApplyMappingFile(proguardMap);
-            })
-            .setMinification(false)
-            .build());
+    AndroidApp instrApp =
+        runR8(
+            ToolHelper.addProguardConfigurationConsumer(
+                    getCommandForInstrumentation(instrOut, flag, NAMING044_JAR, APPLYMAPPING044_JAR)
+                        .setMinification(false),
+                    pgConfig -> pgConfig.setApplyMappingFile(proguardMap))
+                .build());
 
     DexInspector inspector = new DexInspector(instrApp);
     MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN);
@@ -181,14 +181,15 @@
     // keep rules to reserve D and E, along with a proguard map.
     Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "naming001", "keep-rules-105.txt");
     Path proguardMap = out.resolve(MAPPING);
-    AndroidApp outputApp = runR8(
-        getCommandForApps(out, flag, NAMING001_JAR)
-            .addProguardConfigurationConsumer(c -> {
-              c.setPrintMapping(true);
-              c.setPrintMappingFile(proguardMap);
-            })
-            .setMinification(false)
-            .build());
+    AndroidApp outputApp =
+        runR8(
+            ToolHelper.addProguardConfigurationConsumer(
+                    getCommandForApps(out, flag, NAMING001_JAR).setMinification(false),
+                    pgConfig -> {
+                      pgConfig.setPrintMapping(true);
+                      pgConfig.setPrintMappingFile(proguardMap);
+                    })
+                .build());
 
     // Make sure the given proguard map is indeed applied.
     DexInspector inspector = new DexInspector(outputApp);
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 9442559..cca27cf 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -64,19 +64,18 @@
   @Before
   public void generateR8ProcessedApp() throws Exception {
     Path out = temp.getRoot().toPath();
-    R8Command command =
-        R8Command.builder()
-            .setOutput(out, OutputMode.DexIndexed)
-            .addProgramFiles(Paths.get(appFileName))
-            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
-            .addProguardConfigurationConsumer(
-                builder -> {
-                  builder.setPrintMapping(true);
-                  builder.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+    R8Command.Builder builder =
+        ToolHelper.addProguardConfigurationConsumer(
+                R8Command.builder(),
+                pgConfig -> {
+                  pgConfig.setPrintMapping(true);
+                  pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
                 })
-            .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
-            .build();
-    ToolHelper.runR8(command);
+            .setOutput(out, OutputMode.DexIndexed)
+            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
+            .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()));
+    ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(appFileName));
+    ToolHelper.runR8(builder.build());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
index 34f2b76..d5bb4bb 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
@@ -32,18 +32,19 @@
     Path outjar = outdir.resolve("r8_compiled.jar");
     Path proguardMapPath = outdir.resolve("proguard.map");
     ToolHelper.runR8(
-        R8Command.builder()
+        ToolHelper.addProguardConfigurationConsumer(
+                R8Command.builder(),
+                pgConfig -> {
+                  pgConfig.setRenameSourceFileAttribute(TEST_FILE);
+                  pgConfig.addKeepAttributePatterns(
+                      ImmutableList.of("SourceFile", "LineNumberTable"));
+                })
             .addProgramFiles(DEBUGGEE_JAR)
             .setMinApiLevel(minSdk)
             .addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(minSdk)))
             .setMode(CompilationMode.DEBUG)
             .setOutput(outjar, OutputMode.DexIndexed)
             .setProguardMapOutput(proguardMapPath)
-            .addProguardConfigurationConsumer(
-                pg -> {
-                  pg.setRenameSourceFileAttribute(TEST_FILE);
-                  pg.addKeepAttributePatterns(ImmutableList.of("SourceFile", "LineNumberTable"));
-                })
             .build());
     config = new DexDebugTestConfig(outjar);
     config.setProguardMap(proguardMapPath);
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
index 7d963b8..8439bbf 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
@@ -40,7 +40,8 @@
      */
 
     // TODO(66944616): Can we make this work on Dalvik as well?
-    Assume.assumeTrue("Skipping on 4.4.4", ToolHelper.getDexVm().getVersion() != Version.V4_4_4);
+    Assume.assumeTrue("Skipping on VM versions < 4.4.4",
+        ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4));
 
     // Build the second version of LibraryClass
     JasminBuilder compileTimeLibrary = new JasminBuilder();
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index dafd45e..3446242 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -66,14 +66,15 @@
   public void runR8andGetPrintUsage() throws Exception {
     Path out = temp.getRoot().toPath();
     R8Command command =
-        R8Command.builder()
+        ToolHelper.addProguardConfigurationConsumer(
+                R8Command.builder(),
+                pgConfig -> {
+                  pgConfig.setPrintUsage(true);
+                  pgConfig.setPrintUsageFile(out.resolve(test + PRINT_USAGE_FILE_SUFFIX));
+                })
             .setOutput(out, OutputMode.DexIndexed)
             .addProgramFiles(Paths.get(programFile))
             .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
-            .addProguardConfigurationConsumer(builder -> {
-              builder.setPrintUsage(true);
-              builder.setPrintUsageFile(out.resolve(test + PRINT_USAGE_FILE_SUFFIX));
-            })
             .addLibraryFiles(Paths.get(ANDROID_JAR))
             .build();
     ToolHelper.runR8(command, options -> {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index b57a0d8..6d78fd8 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -44,12 +44,11 @@
     Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
     Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
     Path ignoreWarnings = Paths.get(VALID_PROGUARD_DIR, "ignorewarnings.flags");
-    R8.run(
-        R8Command.builder()
-            .addProgramFiles(originalDex)
-            .setOutput(out, OutputMode.DexIndexed)
-            .addProguardConfigurationFiles(keepRules, ignoreWarnings)
-            .build());
+    R8Command.Builder builder = R8Command.builder()
+        .setOutput(out, OutputMode.DexIndexed)
+        .addProguardConfigurationFiles(keepRules, ignoreWarnings);
+    ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+    R8.run(builder.build());
   }
 
   @Test(expected = CompilationFailedException.class)
@@ -67,11 +66,11 @@
         }
       }
     };
-    R8.run(R8Command.builder(handler)
-        .addProgramFiles(originalDex)
+    R8Command.Builder builder = R8Command.builder(handler)
         .setOutput(out, OutputMode.DexIndexed)
-        .addProguardConfigurationFiles(keepRules)
-        .build());
+        .addProguardConfigurationFiles(keepRules);
+    ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+    R8.run(builder.build());
   }
 
   @Test
@@ -89,16 +88,13 @@
       mapping.println("-printmapping mapping.txt");
     }
 
-    ToolHelper.runR8(
-        R8Command.builder()
-            .addProgramFiles(originalDex)
-            .setOutput(out, OutputMode.DexIndexed)
-            .addProguardConfigurationFiles(keepRules, printMapping)
-            .build(),
-        options -> {
-          // Turn off inlining, as we want the mapping that is printed to be stable.
-          options.inlineAccessors = false;
-        });
+    R8Command.Builder builder = R8Command.builder()
+        .setOutput(out, OutputMode.DexIndexed)
+        .addProguardConfigurationFiles(keepRules, printMapping);
+    ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+    // Turn off inlining, as we want the mapping that is printed to be stable.
+    ToolHelper.runR8(builder.build(), options -> options.inlineAccessors = false);
+
     Path outputmapping = out.resolve("mapping.txt");
     String actualMapping;
     actualMapping = new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8);
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 2f5805a..1023667 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -130,23 +130,20 @@
     Path out = temp.getRoot().toPath();
     boolean inline = programFile.contains("inlining");
 
-    R8Command command =
-        R8Command.builder()
-            .setOutput(out, OutputMode.DexIndexed)
-            .addProgramFiles(Paths.get(programFile))
-            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
-            .addProguardConfigurationConsumer(
-                builder -> {
-                  builder.setPrintMapping(true);
-                  builder.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
-                  builder.setOverloadAggressively(minify == MinifyMode.AGGRESSIVE);
-                  builder.setObfuscating(minify.isMinify());
+    R8Command.Builder builder =
+        ToolHelper.addProguardConfigurationConsumer(
+                R8Command.builder(),
+                pgConfig -> {
+                  pgConfig.setPrintMapping(true);
+                  pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+                  pgConfig.setOverloadAggressively(minify == MinifyMode.AGGRESSIVE);
+                  pgConfig.setObfuscating(minify.isMinify());
                 })
-            .addLibraryFiles(JAR_LIBRARIES)
-            .build();
-    ToolHelper.runR8(command, options -> {
-      options.inlineAccessors = inline;
-    });
+            .setOutput(out, OutputMode.DexIndexed)
+            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
+            .addLibraryFiles(JAR_LIBRARIES);
+    ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(programFile));
+    ToolHelper.runR8(builder.build(), options -> options.inlineAccessors = inline);
   }
 
   public static void shaking1HasNoClassUnused(DexInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index ceb14cc..0b6d16b 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -96,16 +96,15 @@
       Consumer<InternalOptions> optionsConsumer) {
     try {
       Path dexOutputDir = temp.newFolder().toPath();
-      R8Command command =
-          R8Command.builder()
-              .addDexProgramData(builder.compile(), EmbeddedOrigin.INSTANCE)
+      R8Command.Builder command =
+          ToolHelper.addProguardConfigurationConsumer(R8Command.builder(), pgConsumer)
               .setOutput(dexOutputDir, OutputMode.DexIndexed)
               .setMode(CompilationMode.DEBUG)
               .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
-              .addProguardConfiguration(proguardConfigurations, Origin.unknown())
-              .addProguardConfigurationConsumer(pgConsumer)
-              .build();
-      ToolHelper.runR8WithFullResult(command, optionsConsumer);
+              .addProguardConfiguration(proguardConfigurations, Origin.unknown());
+      ToolHelper.getAppBuilder(command)
+          .addDexProgramData(builder.compile(), EmbeddedOrigin.INSTANCE);
+      ToolHelper.runR8WithFullResult(command.build(), optionsConsumer);
       return dexOutputDir.resolve("classes.dex");
     } catch (CompilationException | IOException | RecognitionException | ExecutionException
         | CompilationFailedException e) {
@@ -189,8 +188,8 @@
 
   protected int getNumberOfProgramClasses(AndroidApp application) {
     try {
-      return getNumberOfClassesForResources(application.getClassProgramResources())
-          + getNumberOfClassesForResources(application.getDexProgramResources());
+      return getNumberOfClassesForResources(application.getClassProgramResourcesForTesting())
+          + getNumberOfClassesForResources(application.getDexProgramResourcesForTesting());
     } catch (IOException e) {
       return -1;
     }
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
index 64e7a5b..1c6a050 100644
--- a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -45,8 +45,8 @@
   }
 
   private void verifyEmptyCommand(GenerateMainDexListCommand command) throws IOException {
-    assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
-    assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
+    assertEquals(0, ToolHelper.getApp(command).getDexProgramResourcesForTesting().size());
+    assertEquals(0, ToolHelper.getApp(command).getClassProgramResourcesForTesting().size());
     assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
   }
 
diff --git a/tests/api_usage_sample.jar b/tests/api_usage_sample.jar
index 59eb757..f258759 100644
--- a/tests/api_usage_sample.jar
+++ b/tests/api_usage_sample.jar
Binary files differ
diff --git a/tests/d8_api_usage_sample.jar b/tests/d8_api_usage_sample.jar
new file mode 100644
index 0000000..7a1949a
--- /dev/null
+++ b/tests/d8_api_usage_sample.jar
Binary files differ
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
new file mode 100644
index 0000000..5c66037
--- /dev/null
+++ b/tests/r8_api_usage_sample.jar
Binary files differ
diff --git a/third_party/android_jar/lib-v22.tar.gz.sha1 b/third_party/android_jar/lib-v22.tar.gz.sha1
new file mode 100644
index 0000000..9993d73
--- /dev/null
+++ b/third_party/android_jar/lib-v22.tar.gz.sha1
@@ -0,0 +1 @@
+1b29ed8eff486944080c2e75d1835f88ead804ef
\ No newline at end of file
diff --git a/third_party/android_jar/lib-v23.tar.gz.sha1 b/third_party/android_jar/lib-v23.tar.gz.sha1
new file mode 100644
index 0000000..e3ce8c7
--- /dev/null
+++ b/third_party/android_jar/lib-v23.tar.gz.sha1
@@ -0,0 +1 @@
+961bf14c97ab2880998b9f6c6b74f9e459e474fe
\ No newline at end of file
diff --git a/tools/test.py b/tools/test.py
index 8c47219..58fc4eb 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -17,7 +17,7 @@
 import notify
 
 
-ALL_ART_VMS = ["default", "7.0.0", "6.0.1", "5.1.1", "4.4.4"]
+ALL_ART_VMS = ["default", "7.0.0", "6.0.1", "5.1.1", "4.4.4", "4.0.4"]
 BUCKET = 'r8-test-results'
 
 def ParseOptions():