Version 1.7.3-dev

Merge commit '02d1368bd0297f912df0f47d32f2ba639358f7a8' into 1.7
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index ce1b928..9eccf41 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -447,7 +447,7 @@
     }
 
     DesugaredLibraryConfiguration getDesugaredLibraryConfiguration(
-        DexItemFactory factory, boolean libraryCompilation, int minAPILevel) {
+        DexItemFactory factory, boolean libraryCompilation) {
       if (desugaredLibraryConfigurationResources.isEmpty()) {
         return DesugaredLibraryConfiguration.empty();
       }
@@ -456,22 +456,6 @@
       }
       StringResource desugaredLibraryConfigurationResource =
           desugaredLibraryConfigurationResources.get(0);
-
-      // TODO(b/134732760): Remove the following once the default testing hack is not supported
-      // anymore.
-      try {
-        if (desugaredLibraryConfigurationResource.getString().equals("default")) {
-          if (libraryCompilation) {
-            return DesugaredLibraryConfigurationForTesting
-                .configureLibraryDesugaringForLibraryCompilation(minAPILevel, factory);
-          }
-          return DesugaredLibraryConfigurationForTesting
-              .configureLibraryDesugaringForProgramCompilation(minAPILevel, factory);
-        }
-      } catch (ResourceException e) {
-        throw new RuntimeException(e);
-      }
-
       DesugaredLibraryConfigurationParser libraryParser =
           new DesugaredLibraryConfigurationParser(
               factory, getReporter(), libraryCompilation, getMinApiLevel());
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index d6ca3f2..b72548b 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -201,7 +201,7 @@
 
       DexItemFactory factory = new DexItemFactory();
       DesugaredLibraryConfiguration libraryConfiguration =
-          getDesugaredLibraryConfiguration(factory, false, getMinApiLevel());
+          getDesugaredLibraryConfiguration(factory, false);
 
       return new D8Command(
           getAppBuilder().build(),
diff --git a/src/main/java/com/android/tools/r8/DesugaredLibraryConfigurationForTesting.java b/src/main/java/com/android/tools/r8/DesugaredLibraryConfigurationForTesting.java
deleted file mode 100644
index 2f03241..0000000
--- a/src/main/java/com/android/tools/r8/DesugaredLibraryConfigurationForTesting.java
+++ /dev/null
@@ -1,263 +0,0 @@
-// Copyright (c) 2019, 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.graph.DexItemFactory;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.util.List;
-import java.util.Map;
-
-// TODO(134732760): This is still work in progress.
-// The DesugaredLibraryConfigurationForTesting is a set of flags for experimentation
-// with the desugared JDK libraries which should be replaced by a D8/R8
-// API level flag --coreLibraryDescriptor
-public class DesugaredLibraryConfigurationForTesting {
-
-  private static Map<String, String> buildPrefixRewritingForProgramCompilationAllAndroid() {
-    return ImmutableMap.<String, String>builder()
-        .put("java.util.concurrent.ConcurrentHashMap", "j$.util.concurrent.ConcurrentHashMap")
-        .put("java.util.stream.", "j$.util.stream.")
-        .put("java.util.function.", "j$.util.function.")
-        .put("java.util.DoubleSummaryStatistics", "j$.util.DoubleSummaryStatistics")
-        .put("java.util.IntSummaryStatistics", "j$.util.IntSummaryStatistics")
-        .put("java.util.LongSummaryStatistics", "j$.util.LongSummaryStatistics")
-        .put("java.util.Optional", "j$.util.Optional")
-        .put("java.util.PrimitiveIterator", "j$.util.PrimitiveIterator")
-        .put("java.util.Spliterator", "j$.util.Spliterator")
-        .put("java.util.StringJoiner", "j$.util.StringJoiner")
-        .put("java.util.concurrent.ThreadLocalRandom", "j$.util.concurrent.ThreadLocalRandom")
-        .put("java.util.concurrent.atomic.DesugarAtomic", "j$.util.concurrent.atomic.DesugarAtomic")
-        .putAll(buildPrefixRewritingForProgramCompilationAndroidNPlus())
-        .build();
-  }
-
-  private static Map<String, String> buildPrefixRewritingForProgramCompilationAndroidNPlus() {
-    return ImmutableMap.<String, String>builder()
-        .put("java.time.", "j$.time.")
-        .put("java.util.Desugar", "j$.util.Desugar")
-        .build();
-  }
-
-  private static Map<String, String>
-      buildRetargetCoreLibraryMemberForProgramCompilationAllAndroid() {
-    return ImmutableMap.<String, String>builder()
-        .put("java.util.Arrays#stream", "java.util.DesugarArrays")
-        .put("java.util.Arrays#spliterator", "java.util.DesugarArrays")
-        .put("java.util.LinkedHashSet#spliterator", "java.util.DesugarLinkedHashSet")
-        .put(
-            "java.util.concurrent.atomic.AtomicInteger#getAndUpdate",
-            "java.util.concurrent.atomic.DesugarAtomicInteger")
-        .put(
-            "java.util.concurrent.atomic.AtomicInteger#updateAndGet",
-            "java.util.concurrent.atomic.DesugarAtomicInteger")
-        .put(
-            "java.util.concurrent.atomic.AtomicInteger#getAndAccumulate",
-            "java.util.concurrent.atomic.DesugarAtomicInteger")
-        .put(
-            "java.util.concurrent.atomic.AtomicInteger#accumulateAndGet",
-            "java.util.concurrent.atomic.DesugarAtomicInteger")
-        .put(
-            "java.util.concurrent.atomic.AtomicLong#getAndUpdate",
-            "java.util.concurrent.atomic.DesugarAtomicLong")
-        .put(
-            "java.util.concurrent.atomic.AtomicLong#updateAndGet",
-            "java.util.concurrent.atomic.DesugarAtomicLong")
-        .put(
-            "java.util.concurrent.atomic.AtomicLong#getAndAccumulate",
-            "java.util.concurrent.atomic.DesugarAtomicLong")
-        .put(
-            "java.util.concurrent.atomic.AtomicLong#accumulateAndGet",
-            "java.util.concurrent.atomic.DesugarAtomicLong")
-        .put(
-            "java.util.concurrent.atomic.AtomicReference#getAndUpdate",
-            "java.util.concurrent.atomic.DesugarAtomicReference")
-        .put(
-            "java.util.concurrent.atomic.AtomicReference#updateAndGet",
-            "java.util.concurrent.atomic.DesugarAtomicReference")
-        .put(
-            "java.util.concurrent.atomic.AtomicReference#getAndAccumulate",
-            "java.util.concurrent.atomic.DesugarAtomicReference")
-        .put(
-            "java.util.concurrent.atomic.AtomicReference#accumulateAndGet",
-            "java.util.concurrent.atomic.DesugarAtomicReference")
-        .putAll(buildRetargetCoreLibraryMemberForProgramCompilationAndroidNPlus())
-        .build();
-  }
-
-  private static Map<String, String>
-      buildRetargetCoreLibraryMemberForProgramCompilationAndroidNPlus() {
-    // --retarget_core_library_member.
-    return ImmutableMap.<String, String>builder()
-        .put("java.util.Calendar#toInstant", "java.util.DesugarCalendar")
-        .put("java.util.Date#from", "java.util.DesugarDate")
-        .put("java.util.Date#toInstant", "java.util.DesugarDate")
-        .put("java.util.GregorianCalendar#from", "java.util.DesugarGregorianCalendar")
-        .put("java.util.GregorianCalendar#toZonedDateTime", "java.util.DesugarGregorianCalendar")
-        .build();
-  }
-
-  private static Map<String, String> buildPrefixRewritingForCoreLibCompilationAllAndroid() {
-    return ImmutableMap.<String, String>builder()
-        .put("java.util.concurrent.ConcurrentHashMap", "j$.util.concurrent.ConcurrentHashMap")
-        .put("java.util.stream.", "j$.util.stream.")
-        .put("java.util.function.", "j$.util.function.")
-        .put("java.util.Comparators", "j$.util.Comparators")
-        .put("java.util.DoubleSummaryStatistics", "j$.util.DoubleSummaryStatistics")
-        .put("java.util.IntSummaryStatistics", "j$.util.IntSummaryStatistics")
-        .put("java.util.LongSummaryStatistics", "j$.util.LongSummaryStatistics")
-        .put("java.util.Objects", "j$.util.Objects")
-        .put("java.util.Optional", "j$.util.Optional")
-        .put("java.util.PrimitiveIterator", "j$.util.PrimitiveIterator")
-        .put("java.util.SortedSet$1", "j$.util.SortedSet$1")
-        .put("java.util.Spliterator", "j$.util.Spliterator")
-        .put("java.util.StringJoiner", "j$.util.StringJoiner")
-        .put("java.util.Tripwire", "j$.util.Tripwire")
-        .put("java.util.concurrent.DesugarUnsafe", "j$.util.concurrent.DesugarUnsafe")
-        .put("java.util.concurrent.ThreadLocalRandom", "j$.util.concurrent.ThreadLocalRandom")
-        .put("java.util.concurrent.atomic.DesugarAtomic", "j$.util.concurrent.atomic.DesugarAtomic")
-        .putAll(buildPrefixRewritingForCoreLibCompilationAndroidNPlus())
-        .build();
-  }
-
-  private static Map<String, String> buildPrefixRewritingForCoreLibCompilationAndroidNPlus() {
-    return ImmutableMap.<String, String>builder()
-        .put("j$.time.", "java.time.")
-        .put("java.time.", "j$.time.")
-        .put("java.util.Desugar", "j$.util.Desugar")
-        .build();
-  }
-
-  private static Map<String, String> buildRetargetCoreLibraryMemberForCoreLibCompilation() {
-    return ImmutableMap.<String, String>builder()
-        .put("java.util.LinkedHashSet#spliterator", "java.util.DesugarLinkedHashSet")
-        // Following 2 for testing only (core library with extensions).
-        .put("java.util.Arrays#stream", "java.util.DesugarArrays")
-        .put("java.util.Arrays#spliterator", "java.util.DesugarArrays")
-        .build();
-  }
-
-  private static List<String> buildDontRewriteInvocations() {
-    return ImmutableList.of("java.util.Iterator#remove");
-  }
-
-  private static Map<String, String> buildEmulateLibraryInterface() {
-    return ImmutableMap.<String, String>builder()
-        .put("java.util.Map$Entry", "j$.util.Map$Entry")
-        .put("java.util.Collection", "j$.util.Collection")
-        .put("java.util.Map", "j$.util.Map")
-        .put("java.util.Iterator", "j$.util.Iterator")
-        .put("java.util.Comparator", "j$.util.Comparator")
-        // Extra flags: in R8 we marked as emulated all interfaces
-        // with default methods. Emulated interfaces have their
-        // companion class moved to j$ and have a dispatch class.
-        // Bazel instead analyzes the class hierarchy.
-        .put("java.util.List", "j$.util.List")
-        .put("java.util.SortedSet", "j$.util.SortedSet")
-        .put("java.util.Set", "j$.util.Set")
-        .put("java.util.concurrent.ConcurrentMap", "j$.util.concurrent.ConcurrentMap")
-        .build();
-  }
-
-  private static Map<String, String> buildBackportCoreLibraryMembers() {
-    // R8 specific to deal with *8 removal.
-    return ImmutableMap.<String, String>builder()
-        .put("java.lang.Double8", "java.lang.Double")
-        .put("java.lang.Integer8", "java.lang.Integer")
-        .put("java.lang.Long8", "java.lang.Long")
-        .put("java.lang.Math8", "java.lang.Math")
-        .build();
-  }
-
-  public static DesugaredLibraryConfiguration configureLibraryDesugaringForProgramCompilation(
-      int minApiLevel, DexItemFactory factory) {
-    if (minApiLevel >= AndroidApiLevel.O.getLevel()) {
-      return DesugaredLibraryConfiguration.empty();
-    }
-    Map<String, String> rewritePrefix;
-    Map<String, String> retargetCoreLibMember;
-    Map<String, String> emulateLibraryInterface = ImmutableMap.of();
-    List<String> dontRewriteInvocations = ImmutableList.of();
-    if (minApiLevel < AndroidApiLevel.N.getLevel()) {
-      rewritePrefix = buildPrefixRewritingForProgramCompilationAllAndroid();
-      retargetCoreLibMember = buildRetargetCoreLibraryMemberForProgramCompilationAllAndroid();
-      emulateLibraryInterface = buildEmulateLibraryInterface();
-      dontRewriteInvocations = buildDontRewriteInvocations();
-    } else {
-      rewritePrefix = buildPrefixRewritingForProgramCompilationAndroidNPlus();
-      retargetCoreLibMember = buildRetargetCoreLibraryMemberForProgramCompilationAndroidNPlus();
-    }
-    return createDesugaredLibraryConfiguration(
-        factory,
-        false,
-        rewritePrefix,
-        emulateLibraryInterface,
-        retargetCoreLibMember,
-        ImmutableMap.of(),
-        dontRewriteInvocations);
-  }
-
-  public static DesugaredLibraryConfiguration configureLibraryDesugaringForLibraryCompilation(
-      int minApiLevel, DexItemFactory factory) {
-    if (minApiLevel >= AndroidApiLevel.O.getLevel()) {
-      return DesugaredLibraryConfiguration.empty();
-    }
-    Map<String, String> rewritePrefix;
-    Map<String, String> retargetCoreLibMember = ImmutableMap.of();
-    Map<String, String> emulateLibraryInterface = ImmutableMap.of();
-    List<String> dontRewriteInvocations = ImmutableList.of();
-    Map<String, String> backportCoreLibraryMembers = buildBackportCoreLibraryMembers();
-    if (minApiLevel < AndroidApiLevel.N.getLevel()) {
-      retargetCoreLibMember = buildRetargetCoreLibraryMemberForCoreLibCompilation();
-      dontRewriteInvocations = buildDontRewriteInvocations();
-      emulateLibraryInterface = buildEmulateLibraryInterface();
-      rewritePrefix = buildPrefixRewritingForCoreLibCompilationAllAndroid();
-    } else {
-      rewritePrefix = buildPrefixRewritingForCoreLibCompilationAndroidNPlus();
-    }
-    return createDesugaredLibraryConfiguration(
-        factory,
-        true,
-        rewritePrefix,
-        emulateLibraryInterface,
-        retargetCoreLibMember,
-        backportCoreLibraryMembers,
-        dontRewriteInvocations);
-  }
-
-  public static DesugaredLibraryConfiguration createDesugaredLibraryConfiguration(
-      DexItemFactory factory,
-      boolean libraryCompilation,
-      Map<String, String> rewritePrefix,
-      Map<String, String> emulateLibraryInterface,
-      Map<String, String> retargetCoreLibMember,
-      Map<String, String> backportCoreLibraryMembers,
-      List<String> dontRewriteInvocations) {
-    DesugaredLibraryConfiguration.Builder builder = DesugaredLibraryConfiguration.builder(factory);
-    if (libraryCompilation) {
-      builder.setLibraryCompilation();
-    } else {
-      builder.setProgramCompilation();
-    }
-    for (String key : rewritePrefix.keySet()) {
-      builder.putRewritePrefix(key, rewritePrefix.get(key));
-    }
-    for (String key : emulateLibraryInterface.keySet()) {
-      builder.putEmulateLibraryInterface(key, emulateLibraryInterface.get(key));
-    }
-    for (String key : retargetCoreLibMember.keySet()) {
-      builder.putRetargetCoreLibMember(key, retargetCoreLibMember.get(key));
-    }
-    for (String key : backportCoreLibraryMembers.keySet()) {
-      builder.putBackportCoreLibraryMember(key, backportCoreLibraryMembers.get(key));
-    }
-    for (String key : dontRewriteInvocations) {
-      builder.addDontRewriteInvocation(key);
-    }
-    return builder.build();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index d20afac..d1a8e6a 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -213,7 +213,7 @@
 
       DexItemFactory factory = new DexItemFactory();
       DesugaredLibraryConfiguration libraryConfiguration =
-          getDesugaredLibraryConfiguration(factory, true, getMinApiLevel());
+          getDesugaredLibraryConfiguration(factory, true);
 
       R8Command r8Command = null;
       D8Command d8Command = null;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index c986011..5c90ae8 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -447,7 +447,7 @@
       }
 
       DesugaredLibraryConfiguration libraryConfiguration =
-          getDesugaredLibraryConfiguration(factory, false, getMinApiLevel());
+          getDesugaredLibraryConfiguration(factory, false);
 
       ProguardConfigurationParser parser =
           new ProguardConfigurationParser(factory, reporter, allowTestProguardOptions);
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 52963e9..0b02763 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.7.1-dev";
+  public static final String LABEL = "1.7.3-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 77a5da9..b626e56 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -46,6 +47,7 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.ObjectArrays;
@@ -388,46 +390,15 @@
           options.reporter, options.mainDexListConsumer, writeMainDexList(application, namingLens));
       ExceptionUtils.withFinishedResourceHandler(options.reporter, options.mainDexListConsumer);
     }
+
     DataResourceConsumer dataResourceConsumer = options.dataResourceConsumer;
     if (dataResourceConsumer != null) {
+      ImmutableList<DataResourceProvider> dataResourceProviders = application.dataResourceProviders;
       ResourceAdapter resourceAdapter =
           new ResourceAdapter(appView, application.dexItemFactory, graphLense, namingLens, options);
-      Set<String> generatedResourceNames = new HashSet<>();
 
-      for (DataResourceProvider dataResourceProvider : application.dataResourceProviders) {
-        try {
-          dataResourceProvider.accept(
-              new Visitor() {
-                @Override
-                public void visit(DataDirectoryResource directory) {
-                  DataDirectoryResource adapted = resourceAdapter.adaptIfNeeded(directory);
-                  if (adapted != null) {
-                    dataResourceConsumer.accept(adapted, options.reporter);
-                    options.reporter.failIfPendingErrors();
-                  }
-                }
-
-                @Override
-                public void visit(DataEntryResource file) {
-                  if (resourceAdapter.isService(file)) {
-                    // META-INF/services resources are handled below.
-                    return;
-                  }
-
-                  DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
-                  if (generatedResourceNames.add(adapted.getName())) {
-                    dataResourceConsumer.accept(adapted, options.reporter);
-                  } else {
-                    options.reporter.warning(
-                        new StringDiagnostic("Resource '" + file.getName() + "' already exists."));
-                  }
-                  options.reporter.failIfPendingErrors();
-                }
-              });
-        } catch (ResourceException e) {
-          throw new CompilationError(e.getMessage(), e);
-        }
-      }
+      adaptAndPassDataResources(
+          options, dataResourceConsumer, dataResourceProviders, resourceAdapter);
 
       // Write the META-INF/services resources. Sort on service names and keep the order from
       // the input for the implementation lines for deterministic output.
@@ -454,6 +425,60 @@
                 });
       }
     }
+
+    if (options.featureSplitConfiguration != null) {
+      for (DataResourceProvidersAndConsumer entry :
+          options.featureSplitConfiguration.getDataResourceProvidersAndConsumers()) {
+        ResourceAdapter resourceAdapter =
+            new ResourceAdapter(
+                appView, application.dexItemFactory, graphLense, namingLens, options);
+        adaptAndPassDataResources(
+            options, entry.getConsumer(), entry.getProviders(), resourceAdapter);
+      }
+    }
+  }
+
+  private static void adaptAndPassDataResources(
+      InternalOptions options,
+      DataResourceConsumer dataResourceConsumer,
+      Collection<DataResourceProvider> dataResourceProviders,
+      ResourceAdapter resourceAdapter) {
+    Set<String> generatedResourceNames = new HashSet<>();
+
+    for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
+      try {
+        dataResourceProvider.accept(
+            new Visitor() {
+              @Override
+              public void visit(DataDirectoryResource directory) {
+                DataDirectoryResource adapted = resourceAdapter.adaptIfNeeded(directory);
+                if (adapted != null) {
+                  dataResourceConsumer.accept(adapted, options.reporter);
+                  options.reporter.failIfPendingErrors();
+                }
+              }
+
+              @Override
+              public void visit(DataEntryResource file) {
+                if (resourceAdapter.isService(file)) {
+                  // META-INF/services resources are handled below.
+                  return;
+                }
+
+                DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
+                if (generatedResourceNames.add(adapted.getName())) {
+                  dataResourceConsumer.accept(adapted, options.reporter);
+                } else {
+                  options.reporter.warning(
+                      new StringDiagnostic("Resource '" + file.getName() + "' already exists."));
+                }
+                options.reporter.failIfPendingErrors();
+              }
+            });
+      } catch (ResourceException e) {
+        throw new CompilationError(e.getMessage(), e);
+      }
+    }
   }
 
   private static boolean validateProguardMapParses(String content) {
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
index 22e819e..a122ad7 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.features;
 
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResourceProvider;
@@ -14,7 +16,10 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -59,6 +64,49 @@
     return result;
   }
 
+  public static class DataResourceProvidersAndConsumer {
+    private final Set<DataResourceProvider> providers;
+    private final DataResourceConsumer consumer;
+
+    public DataResourceProvidersAndConsumer(
+        Set<DataResourceProvider> providers, DataResourceConsumer consumer) {
+      this.providers = providers;
+      this.consumer = consumer;
+    }
+
+    public Set<DataResourceProvider> getProviders() {
+      return providers;
+    }
+
+    public DataResourceConsumer getConsumer() {
+      return consumer;
+    }
+  }
+
+  public Collection<DataResourceProvidersAndConsumer> getDataResourceProvidersAndConsumers() {
+    List<DataResourceProvidersAndConsumer> result = new ArrayList<>();
+    for (FeatureSplit featureSplit : featureSplits) {
+      DataResourceConsumer dataResourceConsumer =
+          featureSplit.getProgramConsumer().getDataResourceConsumer();
+      if (dataResourceConsumer != null) {
+        Set<DataResourceProvider> dataResourceProviders = new HashSet<>();
+        for (ProgramResourceProvider programResourceProvider :
+            featureSplit.getProgramResourceProviders()) {
+          DataResourceProvider dataResourceProvider =
+              programResourceProvider.getDataResourceProvider();
+          if (dataResourceProvider != null) {
+            dataResourceProviders.add(dataResourceProvider);
+          }
+        }
+        if (!dataResourceProviders.isEmpty()) {
+          result.add(
+              new DataResourceProvidersAndConsumer(dataResourceProviders, dataResourceConsumer));
+        }
+      }
+    }
+    return result;
+  }
+
   public boolean isInFeature(DexProgramClass clazz) {
     return javaTypeToFeatureSplitMapping.containsKey(
         DescriptorUtils.descriptorToJavaType(clazz.type.toDescriptorString()));
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 9a71dc7..e3757da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -33,6 +33,7 @@
   }
 
   public Value getReceiver() {
+    assert inValues.size() > 0;
     return inValues.get(0);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index a75ce9e..46f8bf4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1317,26 +1317,6 @@
       assert code.isConsistentSSA();
     }
 
-    if (nonNullTracker != null) {
-      // TODO(b/139246447): Once we extend this optimization to, e.g., constants of primitive args,
-      //   this may not be the right place to collect call site optimization info.
-      // Collecting call-site optimization info depends on the existence of non-null IRs.
-      // Arguments can be changed during the debug mode.
-      if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
-        appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
-      }
-      // Computation of non-null parameters on normal exits rely on the existence of non-null IRs.
-      nonNullTracker.computeNonNullParamOnNormalExits(feedback, code);
-    }
-    if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) {
-      codeRewriter.removeAssumeInstructions(code);
-      assert code.isConsistentSSA();
-    }
-    // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL.
-    assert code.verifyNoNullabilityBottomTypes();
-
-    assert code.verifyTypes(appView);
-
     previous = printMethod(code, "IR after outline handler (SSA)", previous);
 
     // TODO(mkroghj) Test if shorten live ranges is worth it.
@@ -1362,6 +1342,26 @@
       classStaticizer.examineMethodCode(method, code);
     }
 
+    if (nonNullTracker != null) {
+      // TODO(b/139246447): Once we extend this optimization to, e.g., constants of primitive args,
+      //   this may not be the right place to collect call site optimization info.
+      // Collecting call-site optimization info depends on the existence of non-null IRs.
+      // Arguments can be changed during the debug mode.
+      if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
+        appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
+      }
+      // Computation of non-null parameters on normal exits rely on the existence of non-null IRs.
+      nonNullTracker.computeNonNullParamOnNormalExits(feedback, code);
+    }
+    if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) {
+      codeRewriter.removeAssumeInstructions(code);
+      assert code.isConsistentSSA();
+    }
+    // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL.
+    assert code.verifyNoNullabilityBottomTypes();
+
+    assert code.verifyTypes(appView);
+
     if (appView.enableWholeProgramOptimizations()) {
       if (libraryMethodOverrideAnalysis != null) {
         libraryMethodOverrideAnalysis.analyze(code);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index b796600..f6f34e6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1056,6 +1056,12 @@
       addProvider(
           new MethodGenerator(
               method, BackportedMethods::CollectionMethods_mapOfEntries, "ofEntries"));
+
+      // Map.Entry<K, V> Map.entry(K, V)
+      type = factory.mapType;
+      proto = factory.createProto(factory.mapEntryType, factory.objectType, factory.objectType);
+      method = factory.createMethod(type, proto, "entry");
+      addProvider(new MethodGenerator(method, BackportedMethods::CollectionMethods_mapEntry));
     }
 
     private void initializeJava11MethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 0922cd2..9f22ded 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -19,9 +19,12 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 
 // TODO(b/134732760): In progress.
 // I convert library calls with desugared parameters/return values so they can work normally.
@@ -72,13 +75,17 @@
       if (dexClass == null || !dexClass.isLibraryClass()) {
         continue;
       }
+      // Library methods do not understand desugared types, hence desugared types have to be
+      // converted around non desugared library calls for the invoke to resolve.
       if (appView.rewritePrefix.hasRewrittenType(invokedMethod.proto.returnType)) {
-        addReturnConversion(code, invokeMethod, iterator);
+        rewriteLibraryInvoke(code, invokeMethod, iterator);
+        continue;
       }
       for (int i = 0; i < invokedMethod.proto.parameters.values.length; i++) {
         DexType argType = invokedMethod.proto.parameters.values[i];
         if (appView.rewritePrefix.hasRewrittenType(argType)) {
-          addParameterConversion(code, invokeMethod, iterator, argType, i);
+          rewriteLibraryInvoke(code, invokeMethod, iterator);
+          continue;
         }
       }
     }
@@ -109,117 +116,126 @@
     return vivifiedType;
   }
 
-  private void addParameterConversion(
-      IRCode code,
-      InvokeMethod invokeMethod,
-      InstructionListIterator iterator,
-      DexType argType,
-      int parameter) {
-    if (!appView
-        .options()
-        .desugaredLibraryConfiguration
-        .getCustomConversions()
-        .containsKey(argType)) {
-      // TODO(b/134732760): Add Wrapper Conversions.
-      warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter");
-      return;
-    }
-
-    Value inValue = invokeMethod.inValues().get(parameter);
-    DexType argVivifiedType = vivifiedTypeFor(argType);
-    DexType conversionHolder =
-        appView.options().desugaredLibraryConfiguration.getCustomConversions().get(argType);
-
-    // ConversionType has static method "type convert(rewrittenType)".
-    // But everything is going to be rewritten, so we need to call "vivifiedType convert(type)".
-    DexMethod conversionMethod =
-        factory.createMethod(
-            conversionHolder,
-            factory.createProto(argVivifiedType, argType),
-            factory.convertMethodName);
-    Value convertedValue =
-        code.createValue(
-            TypeLatticeElement.fromDexType(
-                argVivifiedType, inValue.getTypeLattice().nullability(), appView));
-    InvokeStatic conversionInstruction =
-        new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
-    conversionInstruction.setPosition(invokeMethod.getPosition());
-    iterator.previous();
-    iterator.add(conversionInstruction);
-    iterator.next();
-
-    // Rewrite invoke (signature and inValue to rewrite).
-    DexMethod newDexMethod =
-        dexMethodWithDifferentParameter(
-            invokeMethod.getInvokedMethod(), argVivifiedType, parameter);
-    Invoke newInvokeMethod =
-        Invoke.create(
-            invokeMethod.getType(),
-            newDexMethod,
-            newDexMethod.proto,
-            invokeMethod.outValue(),
-            invokeMethod.inValues());
-    newInvokeMethod.replaceValue(parameter, conversionInstruction.outValue());
-    iterator.replaceCurrentInstruction(newInvokeMethod);
-  }
-
-  private void addReturnConversion(
+  private void rewriteLibraryInvoke(
       IRCode code, InvokeMethod invokeMethod, InstructionListIterator iterator) {
-    DexType returnType = invokeMethod.getReturnType();
-    if (!appView
-        .options()
-        .desugaredLibraryConfiguration
-        .getCustomConversions()
-        .containsKey(returnType)) {
-      // TODO(b/134732760): Add Wrapper Conversions.
-      warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return");
-      return;
+    DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+
+    // Create return conversion if required.
+    Instruction returnConversion = null;
+    DexType newReturnType;
+    DexType returnType = invokedMethod.proto.returnType;
+    if (appView.rewritePrefix.hasRewrittenType(returnType)) {
+      if (appView
+          .options()
+          .desugaredLibraryConfiguration
+          .getCustomConversions()
+          .containsKey(returnType)) {
+        newReturnType = vivifiedTypeFor(returnType);
+        // Return conversion added only if return value is used.
+        if (invokeMethod.outValue() != null
+            && invokeMethod.outValue().numberOfUsers() + invokeMethod.outValue().numberOfPhiUsers()
+                > 0) {
+          returnConversion =
+              createReturnConversionAndReplaceUses(code, invokeMethod, returnType, newReturnType);
+        }
+      } else {
+        // TODO(b/134732760): Add Wrapper Conversions.
+        warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return");
+        newReturnType = returnType;
+      }
+    } else {
+      newReturnType = returnType;
     }
 
-    DexType returnVivifiedType = vivifiedTypeFor(returnType);
-    DexType conversionHolder =
-        appView.options().desugaredLibraryConfiguration.getCustomConversions().get(returnType);
+    // Create parameter conversions if required.
+    List<Instruction> parameterConversions = new ArrayList<>();
+    List<Value> newInValues = new ArrayList<>();
+    if (invokeMethod.isInvokeMethodWithReceiver()) {
+      assert !appView.rewritePrefix.hasRewrittenType(invokedMethod.holder);
+      newInValues.add(invokeMethod.asInvokeMethodWithReceiver().getReceiver());
+    }
+    int receiverShift = BooleanUtils.intValue(invokeMethod.isInvokeMethodWithReceiver());
+    DexType[] parameters = invokedMethod.proto.parameters.values;
+    DexType[] newParameters = parameters.clone();
+    for (int i = 0; i < parameters.length; i++) {
+      DexType argType = parameters[i];
+      if (appView.rewritePrefix.hasRewrittenType(argType)) {
+        if (appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getCustomConversions()
+            .containsKey(argType)) {
+          DexType argVivifiedType = vivifiedTypeFor(argType);
+          Value inValue = invokeMethod.inValues().get(i + receiverShift);
+          newParameters[i] = argVivifiedType;
+          parameterConversions.add(
+              createParameterConversion(code, argType, argVivifiedType, inValue));
+          newInValues.add(parameterConversions.get(parameterConversions.size() - 1).outValue());
+        } else {
+          // TODO(b/134732760): Add Wrapper Conversions.
+          warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter");
+          newInValues.add(invokeMethod.inValues().get(i + receiverShift));
+        }
+      } else {
+        newInValues.add(invokeMethod.inValues().get(i + receiverShift));
+      }
+    }
 
-    // ConversionType has static method "rewrittenType convert(type)".
-    // But everything is going to be rewritten, so we need to call "type convert(vivifiedType)".
-    DexMethod conversionMethod =
-        factory.createMethod(
-            conversionHolder,
-            factory.createProto(returnType, returnVivifiedType),
-            factory.convertMethodName);
-    Value convertedValue =
-        code.createValue(
-            TypeLatticeElement.fromDexType(returnType, Nullability.maybeNull(), appView));
-    invokeMethod.outValue().replaceUsers(convertedValue);
-    InvokeStatic conversionInstruction =
-        new InvokeStatic(
-            conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue()));
-    conversionInstruction.setPosition(invokeMethod.getPosition());
-
-    // Rewrite invoke (signature to rewrite).
+    // Patch the invoke with new types and new inValues.
+    DexProto newProto = factory.createProto(newReturnType, newParameters);
     DexMethod newDexMethod =
-        dexMethodWithDifferentReturn(invokeMethod.getInvokedMethod(), returnVivifiedType);
+        factory.createMethod(invokedMethod.holder, newProto, invokedMethod.name);
     Invoke newInvokeMethod =
         Invoke.create(
             invokeMethod.getType(),
             newDexMethod,
             newDexMethod.proto,
             invokeMethod.outValue(),
-            invokeMethod.inValues());
+            newInValues);
+
+    // Insert and reschedule all instructions.
+    iterator.previous();
+    for (Instruction parameterConversion : parameterConversions) {
+      parameterConversion.setPosition(invokeMethod.getPosition());
+      iterator.add(parameterConversion);
+    }
+    assert iterator.peekNext() == invokeMethod;
+    iterator.next();
     iterator.replaceCurrentInstruction(newInvokeMethod);
-    iterator.add(conversionInstruction);
+    if (returnConversion != null) {
+      returnConversion.setPosition(invokeMethod.getPosition());
+      iterator.add(returnConversion);
+    }
   }
 
-  private DexMethod dexMethodWithDifferentParameter(
-      DexMethod method, DexType newParameterType, int parameter) {
-    DexType[] newParameters = method.proto.parameters.values.clone();
-    newParameters[parameter] = newParameterType;
-    DexProto newProto = factory.createProto(method.proto.returnType, newParameters);
-    return factory.createMethod(method.holder, newProto, method.name);
+  private Instruction createParameterConversion(
+      IRCode code, DexType argType, DexType argVivifiedType, Value inValue) {
+    DexMethod conversionMethod = createConversionMethod(argType, argType, argVivifiedType);
+    // The value is null only if the input is null.
+    Value convertedValue =
+        createConversionValue(code, inValue.getTypeLattice().nullability(), argVivifiedType);
+    return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
   }
 
-  private DexMethod dexMethodWithDifferentReturn(DexMethod method, DexType newReturnType) {
-    DexProto newProto = factory.createProto(newReturnType, method.proto.parameters.values);
-    return factory.createMethod(method.holder, newProto, method.name);
+  private Instruction createReturnConversionAndReplaceUses(
+      IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
+    DexMethod conversionMethod = createConversionMethod(returnType, returnVivifiedType, returnType);
+    Value convertedValue = createConversionValue(code, Nullability.maybeNull(), returnType);
+    invokeMethod.outValue().replaceUsers(convertedValue);
+    return new InvokeStatic(
+        conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue()));
+  }
+
+  private DexMethod createConversionMethod(DexType type, DexType srcType, DexType destType) {
+    // ConversionType holds the methods "rewrittenType convert(type)" and the other way around.
+    // But everything is going to be rewritten, so we need to use vivifiedType and type".
+    DexType conversionHolder =
+        appView.options().desugaredLibraryConfiguration.getCustomConversions().get(type);
+    return factory.createMethod(
+        conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName);
+  }
+
+  private Value createConversionValue(IRCode code, Nullability nullability, DexType valueType) {
+    return code.createValue(TypeLatticeElement.fromDexType(valueType, nullability, appView));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index aadf9e5..864099b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -597,6 +597,60 @@
         ImmutableList.of());
   }
 
+  public static CfCode CollectionMethods_mapEntry(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfNew(
+                options.itemFactory.createType("Ljava/util/AbstractMap$SimpleImmutableEntry;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfLoad(ValueType.OBJECT, 0),
+            label1,
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/Objects;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/Object;"),
+                        options.itemFactory.createType("Ljava/lang/Object;")),
+                    options.itemFactory.createString("requireNonNull")),
+                false),
+            new CfLoad(ValueType.OBJECT, 1),
+            label2,
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/Objects;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/Object;"),
+                        options.itemFactory.createType("Ljava/lang/Object;")),
+                    options.itemFactory.createString("requireNonNull")),
+                false),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/AbstractMap$SimpleImmutableEntry;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("V"),
+                        options.itemFactory.createType("Ljava/lang/Object;"),
+                        options.itemFactory.createType("Ljava/lang/Object;")),
+                    options.itemFactory.createString("<init>")),
+                false),
+            label3,
+            new CfReturn(ValueType.OBJECT),
+            label4),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode CollectionMethods_mapOfEntries(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 0008162..45f8c45 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -5,7 +5,9 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Assume;
@@ -89,6 +91,15 @@
         continue;
       }
       if (instruction.isInvokeMethod()) {
+        // For virtual and interface calls, proceed on valid results only (since it's enforced).
+        if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
+          DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
+          ResolutionResult resolutionResult =
+              appView.appInfo().resolveMethod(invokedMethod.holder, invokedMethod);
+          if (!resolutionResult.isValidVirtualTarget(appView.options())) {
+            continue;
+          }
+        }
         Collection<DexEncodedMethod> targets =
             instruction.asInvokeMethod().lookupTargets(appView, context.method.holder);
         if (targets == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 88a20da..1e7d21a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.Arrays;
@@ -43,6 +44,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
+import java.util.function.Predicate;
 
 public final class ClassStaticizer {
 
@@ -230,8 +232,8 @@
       // We are inside an instance method of candidate class (not an instance initializer
       // which we will check later), check if all the references to 'this' are valid
       // (the call will invalidate the candidate if some of them are not valid).
-      analyzeAllValueUsers(receiverClassCandidateInfo,
-          receiverValue, factory.isConstructor(method.method));
+      analyzeAllValueUsers(
+          receiverClassCandidateInfo, receiverValue, factory.isConstructor(method.method));
 
       // If the candidate is still valid, ignore all instructions
       // we treat as valid usages on receiver.
@@ -289,7 +291,7 @@
           // If the candidate still valid, ignore all usages in further analysis.
           Value value = instruction.outValue();
           if (value != null) {
-            alreadyProcessed.addAll(value.uniqueUsers());
+            alreadyProcessed.addAll(value.aliasedUsers());
           }
         }
         continue;
@@ -433,14 +435,16 @@
         appView.appInfo().lookupDirectTarget(invoke.getInvokedMethod());
     List<Value> values = invoke.inValues();
 
-    if (values.lastIndexOf(candidateValue) != 0 ||
-        methodInvoked == null || methodInvoked.method.holder != candidateType) {
+    if (ListUtils.lastIndexMatching(values, v -> v.getAliasedValue() == candidateValue) != 0
+        || methodInvoked == null
+        || methodInvoked.method.holder != candidateType) {
       return false;
     }
 
     // Check arguments.
     for (int i = 1; i < values.size(); i++) {
-      if (!values.get(i).definition.isConstInstruction()) {
+      Value arg = values.get(i).getAliasedValue();
+      if (arg.isPhi() || !arg.definition.isConstInstruction()) {
         return false;
       }
     }
@@ -484,40 +488,54 @@
 
   private CandidateInfo analyzeAllValueUsers(
       CandidateInfo candidateInfo, Value value, boolean ignoreSuperClassInitInvoke) {
-    assert value != null;
+    assert value != null && value == value.getAliasedValue();
 
     if (value.numberOfPhiUsers() > 0) {
       return candidateInfo.invalidate();
     }
 
-    for (Instruction user : value.uniqueUsers()) {
-      if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
-        InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
-        DexMethod methodReferenced = invoke.getInvokedMethod();
-        if (factory.isConstructor(methodReferenced)) {
-          assert user.isInvokeDirect();
-          if (ignoreSuperClassInitInvoke &&
-              invoke.inValues().lastIndexOf(value) == 0 &&
-              methodReferenced == factory.objectMethods.constructor) {
-            // If we are inside candidate constructor and analyzing usages
-            // of the receiver, we want to ignore invocations of superclass
-            // constructor which will be removed after staticizing.
-            continue;
+    Set<Instruction> currentUsers = value.uniqueUsers();
+    while (!currentUsers.isEmpty()) {
+      Set<Instruction> indirectUsers = Sets.newIdentityHashSet();
+      for (Instruction user : currentUsers) {
+        if (user.isAssume()) {
+          if (user.outValue().numberOfPhiUsers() > 0) {
+            return candidateInfo.invalidate();
           }
-          return candidateInfo.invalidate();
-        }
-        AppInfo appInfo = appView.appInfo();
-        DexEncodedMethod methodInvoked = user.isInvokeDirect()
-            ? appInfo.lookupDirectTarget(methodReferenced)
-            : appInfo.lookupVirtualTarget(methodReferenced.holder, methodReferenced);
-        if (invoke.inValues().lastIndexOf(value) == 0 &&
-            methodInvoked != null && methodInvoked.method.holder == candidateInfo.candidate.type) {
+          indirectUsers.addAll(user.outValue().uniqueUsers());
           continue;
         }
-      }
+        if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
+          InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+          Predicate<Value> isAliasedValue = v -> v.getAliasedValue() == value;
+          DexMethod methodReferenced = invoke.getInvokedMethod();
+          if (factory.isConstructor(methodReferenced)) {
+            assert user.isInvokeDirect();
+            if (ignoreSuperClassInitInvoke
+                && ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
+                && methodReferenced == factory.objectMethods.constructor) {
+              // If we are inside candidate constructor and analyzing usages
+              // of the receiver, we want to ignore invocations of superclass
+              // constructor which will be removed after staticizing.
+              continue;
+            }
+            return candidateInfo.invalidate();
+          }
+          AppInfo appInfo = appView.appInfo();
+          DexEncodedMethod methodInvoked = user.isInvokeDirect()
+              ? appInfo.lookupDirectTarget(methodReferenced)
+              : appInfo.lookupVirtualTarget(methodReferenced.holder, methodReferenced);
+          if (ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
+              && methodInvoked != null
+              && methodInvoked.method.holder == candidateInfo.candidate.type) {
+            continue;
+          }
+        }
 
-      // All other users are not allowed.
-      return candidateInfo.invalidate();
+        // All other users are not allowed.
+        return candidateInfo.invalidate();
+      }
+      currentUsers = indirectUsers;
     }
 
     return candidateInfo;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 1ab806d..3956609 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -340,10 +340,11 @@
       }
       Set<Phi> chainedPhis = Sets.newIdentityHashSet();
       for (Value operand : phi.getOperands()) {
-        if (operand.isPhi()) {
+        Value v = operand.getAliasedValue();
+        if (v.isPhi()) {
           chainedPhis.add(operand.asPhi());
         } else {
-          if (operand != thisValue) {
+          if (v != thisValue) {
             return false;
           }
         }
@@ -360,7 +361,7 @@
 
   // Fixup `this` usages: rewrites all method calls so that they point to static methods.
   private void fixupStaticizedThisUsers(IRCode code, Value thisValue) {
-    assert thisValue != null;
+    assert thisValue != null && thisValue == thisValue.getAliasedValue();
     // Depending on other optimizations, e.g., inlining, `this` can be flown to phis.
     Set<Phi> trivialPhis = Sets.newIdentityHashSet();
     boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfThis(
@@ -368,10 +369,10 @@
     assert thisValue.numberOfPhiUsers() == 0 || onlyHasTrivialPhis;
     assert trivialPhis.isEmpty() || onlyHasTrivialPhis;
 
-    Set<Instruction> users = SetUtils.newIdentityHashSet(thisValue.uniqueUsers());
+    Set<Instruction> users = SetUtils.newIdentityHashSet(thisValue.aliasedUsers());
     // If that is the case, method calls we want to fix up include users of those phis.
     for (Phi phi : trivialPhis) {
-      users.addAll(phi.uniqueUsers());
+      users.addAll(phi.aliasedUsers());
     }
 
     fixupStaticizedValueUsers(code, users);
@@ -425,13 +426,14 @@
       }
       Set<Phi> chainedPhis = Sets.newIdentityHashSet();
       for (Value operand : phi.getOperands()) {
-        if (operand.isPhi()) {
+        Value v = operand.getAliasedValue();
+        if (v.isPhi()) {
           chainedPhis.add(operand.asPhi());
         } else {
-          if (!operand.definition.isStaticGet()) {
+          if (!v.definition.isStaticGet()) {
             return false;
           }
-          if (operand.definition.asStaticGet().getField() != field) {
+          if (v.definition.asStaticGet().getField() != field) {
             return false;
           }
         }
@@ -458,10 +460,10 @@
     assert dest.numberOfPhiUsers() == 0 || onlyHasTrivialPhis;
     assert trivialPhis.isEmpty() || onlyHasTrivialPhis;
 
-    Set<Instruction> users = SetUtils.newIdentityHashSet(dest.uniqueUsers());
+    Set<Instruction> users = SetUtils.newIdentityHashSet(dest.aliasedUsers());
     // If that is the case, method calls we want to fix up include users of those phis.
     for (Phi phi : trivialPhis) {
-      users.addAll(phi.uniqueUsers());
+      users.addAll(phi.aliasedUsers());
     }
 
     fixupStaticizedValueUsers(code, users);
@@ -475,6 +477,9 @@
 
   private void fixupStaticizedValueUsers(IRCode code, Set<Instruction> users) {
     for (Instruction user : users) {
+      if (user.isAssume()) {
+        continue;
+      }
       assert user.isInvokeVirtual() || user.isInvokeDirect();
       InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
       Value newValue = null;
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 8b7004e..18f8459 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -157,7 +157,7 @@
           field.type.toSourceString());
     }
 
-    DexField toDexField(DexItemFactory factory, DexType clazz) {
+    public DexField toDexField(DexItemFactory factory, DexType clazz) {
       return factory.createField(
           clazz,
           factory.createType(javaTypeToDescriptor(type)),
@@ -266,7 +266,7 @@
       return name.substring(0, name.lastIndexOf(JAVA_PACKAGE_SEPARATOR));
     }
 
-    DexMethod toDexMethod(DexItemFactory factory, DexType clazz) {
+    public DexMethod toDexMethod(DexItemFactory factory, DexType clazz) {
       DexType[] paramTypes = new DexType[parameters.length];
       for (int i = 0; i < parameters.length; i++) {
         paramTypes[i] = factory.createType(javaTypeToDescriptor(parameters[i]));
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 04d7501..bde48e7 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
+import static com.android.tools.r8.utils.SymbolGenerationUtils.PRIMITIVE_TYPE_NAMES;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
@@ -106,17 +107,19 @@
     String nextName(char[] packagePrefix, InternalNamingState state, boolean isDirectMethodCall) {
       StringBuilder nextName = new StringBuilder();
       nextName.append(packagePrefix);
-      if (state.getDictionaryIndex() < obfuscationDictionary.size()) {
-        nextName.append(obfuscationDictionary.get(state.incrementDictionaryIndex()));
-      } else {
-        String nextString;
-        do {
-          nextString =
-              SymbolGenerationUtils.numberToIdentifier(
-                  state.incrementNameIndex(isDirectMethodCall), mixedCasing);
-        } while (obfuscationDictionaryForLookup.contains(nextString));
-        nextName.append(nextString);
-      }
+      String nextString;
+      do {
+        if (state.getDictionaryIndex() < obfuscationDictionary.size()) {
+          nextString = obfuscationDictionary.get(state.incrementDictionaryIndex());
+        } else {
+          do {
+            nextString =
+                SymbolGenerationUtils.numberToIdentifier(
+                    state.incrementNameIndex(isDirectMethodCall), mixedCasing);
+          } while (obfuscationDictionaryForLookup.contains(nextString));
+        }
+      } while (PRIMITIVE_TYPE_NAMES.contains(nextString));
+      nextName.append(nextString);
       return nextName.toString();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 1eb64de..2e7ed67 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -593,11 +593,13 @@
 
   private class UseRegistry extends com.android.tools.r8.graph.UseRegistry {
 
+    private final DexProgramClass currentHolder;
     private final DexEncodedMethod currentMethod;
 
     private UseRegistry(DexItemFactory factory, DexProgramClass holder, DexEncodedMethod method) {
       super(factory);
       assert holder.type == method.method.holder;
+      this.currentHolder = holder;
       this.currentMethod = method;
     }
 
@@ -607,7 +609,7 @@
 
     @Override
     public boolean registerInvokeVirtual(DexMethod method) {
-      return registerInvokeVirtual(method, KeepReason.invokedFrom(currentMethod));
+      return registerInvokeVirtual(method, KeepReason.invokedFrom(currentHolder, currentMethod));
     }
 
     boolean registerInvokeVirtual(DexMethod method, KeepReason keepReason) {
@@ -632,7 +634,7 @@
 
     @Override
     public boolean registerInvokeDirect(DexMethod method) {
-      return registerInvokeDirect(method, KeepReason.invokedFrom(currentMethod));
+      return registerInvokeDirect(method, KeepReason.invokedFrom(currentHolder, currentMethod));
     }
 
     boolean registerInvokeDirect(DexMethod method, KeepReason keepReason) {
@@ -648,7 +650,7 @@
 
     @Override
     public boolean registerInvokeStatic(DexMethod method) {
-      return registerInvokeStatic(method, KeepReason.invokedFrom(currentMethod));
+      return registerInvokeStatic(method, KeepReason.invokedFrom(currentHolder, currentMethod));
     }
 
     boolean registerInvokeStatic(DexMethod method, KeepReason keepReason) {
@@ -683,7 +685,7 @@
 
     @Override
     public boolean registerInvokeInterface(DexMethod method) {
-      return registerInvokeInterface(method, KeepReason.invokedFrom(currentMethod));
+      return registerInvokeInterface(method, KeepReason.invokedFrom(currentHolder, currentMethod));
     }
 
     boolean registerInvokeInterface(DexMethod method, KeepReason keepReason) {
@@ -2028,7 +2030,7 @@
         .createMethod(enumClass.type, proto, appView.dexItemFactory().createString("values"));
   }
 
-  private void markEnumValuesAsReachable(DexClass clazz, KeepReason reason) {
+  private void markEnumValuesAsReachable(DexProgramClass clazz, KeepReason reason) {
     DexEncodedMethod valuesMethod = clazz.lookupMethod(generatedEnumValuesMethod(clazz));
     if (valuesMethod != null) {
       // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
@@ -2847,10 +2849,11 @@
     // SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will
     // call this method.
     if (invoke.inValues().get(0).isConstClass()) {
-      DexClass clazz =
-          appView.definitionFor(invoke.inValues().get(0).definition.asConstClass().getValue());
-      if (clazz.accessFlags.isEnum() && clazz.superType == appView.dexItemFactory().enumType) {
-        markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method));
+      DexType type = invoke.inValues().get(0).definition.asConstClass().getValue();
+      DexProgramClass clazz = getProgramClassOrNull(type);
+      if (clazz != null && clazz.accessFlags.isEnum()) {
+        DexProgramClass holder = getProgramClassOrNull(method.method.holder);
+        markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(holder, method));
       }
     }
   }
@@ -3023,8 +3026,8 @@
           // will use the values() method on that enum class.
           if (options.isGeneratingClassFiles()
               && annotationHolder == dexItemFactory.annotationDefault) {
-            DexClass clazz = appView.definitionFor(field.type);
-            if (clazz != null && clazz.isProgramClass() && clazz.accessFlags.isEnum()) {
+            DexProgramClass clazz = getProgramClassOrNull(field.type);
+            if (clazz != null && clazz.accessFlags.isEnum()) {
               markEnumValuesAsReachable(clazz, KeepReason.referencedInAnnotation(annotationHolder));
             }
           }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index a59f9e1..eae4a9b 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -43,8 +43,8 @@
     return new ReachableFromLiveType(type);
   }
 
-  public static KeepReason invokedFrom(DexEncodedMethod method) {
-    return new InvokedFrom(method);
+  public static KeepReason invokedFrom(DexProgramClass holder, DexEncodedMethod method) {
+    return new InvokedFrom(holder, method);
   }
 
   public static KeepReason invokedFromLambdaCreatedIn(DexEncodedMethod method) {
@@ -252,8 +252,9 @@
 
   private static class InvokedFrom extends BasedOnOtherMethod {
 
-    private InvokedFrom(DexEncodedMethod method) {
+    private InvokedFrom(DexProgramClass holder, DexEncodedMethod method) {
       super(method);
+      assert holder.type == method.method.holder;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 9c135c8..d78cb45 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -211,8 +211,7 @@
   public boolean enableNameReflectionOptimization = true;
   public boolean enableStringConcatenationOptimization = true;
   public boolean enableTreeShakingOfLibraryMethodOverrides = false;
-  // TODO(b/139246447): enable after branching.
-  public boolean enableCallSiteOptimizationInfoPropagation = false;
+  public boolean enableCallSiteOptimizationInfoPropagation = true;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
 
@@ -354,7 +353,12 @@
     }
     if (featureSplitConfiguration != null) {
       for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
-        featureSplit.getProgramConsumer().finished(reporter);
+        ProgramConsumer programConsumer = featureSplit.getProgramConsumer();
+        programConsumer.finished(reporter);
+        DataResourceConsumer dataResourceConsumer = programConsumer.getDataResourceConsumer();
+        if (dataResourceConsumer != null) {
+          dataResourceConsumer.finished(reporter);
+        }
       }
     }
   }
@@ -1015,13 +1019,6 @@
     enableNameReflectionOptimization = false;
   }
 
-  // TODO(b/139246447): Remove this once enabled.
-  @VisibleForTesting
-  public void enableCallSiteOptimizationInfoPropagation() {
-    assert !enableCallSiteOptimizationInfoPropagation;
-    enableCallSiteOptimizationInfoPropagation = true;
-  }
-
   private boolean hasMinApi(AndroidApiLevel level) {
     assert isGeneratingDex();
     return minApiLevel >= level.getLevel();
diff --git a/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
index b66ca5d..9e23205 100644
--- a/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
@@ -6,7 +6,9 @@
 
 import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
 
+import com.google.common.collect.Sets;
 import java.util.Arrays;
+import java.util.Set;
 
 public class SymbolGenerationUtils {
 
@@ -15,6 +17,9 @@
     DONT_USE_MIXED_CASE
   }
 
+  public static Set<String> PRIMITIVE_TYPE_NAMES =
+      Sets.newHashSet("boolean", "byte", "char", "double", "float", "int", "long", "short", "void");
+
   // These letters are used not creating fresh names to output and not for parsing dex/class files.
   private static final char[] IDENTIFIER_CHARACTERS =
       "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
diff --git a/src/test/desugaredLibraryConversions/stubs/Duration.java b/src/test/desugaredLibraryConversions/stubs/Duration.java
index e559a8f..e49693f 100644
--- a/src/test/desugaredLibraryConversions/stubs/Duration.java
+++ b/src/test/desugaredLibraryConversions/stubs/Duration.java
@@ -9,7 +9,7 @@
     return null;
   }
 
-  public int getSeconds() {
+  public long getSeconds() {
     return 0;
   }
 
diff --git a/src/test/examplesJava9/backport/MapBackportJava9Main.java b/src/test/examplesJava9/backport/MapBackportJava9Main.java
index 2aa5cfd..442fd9b 100644
--- a/src/test/examplesJava9/backport/MapBackportJava9Main.java
+++ b/src/test/examplesJava9/backport/MapBackportJava9Main.java
@@ -15,6 +15,7 @@
     testOf2();
     testOf10();
     testOfEntries();
+    testEntry();
   }
 
   private static void testOf0() {
@@ -218,6 +219,31 @@
     }
   }
 
+  private static void testEntry() {
+    Object key = new Object();
+    Object value = new Object();
+    Map.Entry<Object, Object> entry = Map.entry(key, value);
+    assertSame(key, entry.getKey());
+    assertSame(value, entry.getValue());
+
+    try {
+      entry.setValue(new Object());
+      throw new AssertionError();
+    } catch (UnsupportedOperationException expected) {
+    }
+
+    try {
+      Map.entry(null, value);
+      throw new AssertionError();
+    } catch (NullPointerException expected) {
+    }
+    try {
+      Map.entry(key, null);
+      throw new AssertionError();
+    } catch (NullPointerException expected) {
+    }
+  }
+
   private static void assertMutationNotAllowed(Map<Object, Object> ofObject) {
     try {
       ofObject.put(new Object(), new Object());
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2819cc7..4256f09 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -162,6 +162,10 @@
   public static final Path DESUGAR_LIB_JSON_FOR_TESTING =
       Paths.get("src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json");
 
+  public static boolean isLocalDevelopment() {
+    return System.getProperty("local_development", "0").equals("1");
+  }
+
   public static boolean shouldRunSlowTests() {
     return System.getProperty("slow_tests", "0").equals("1");
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/JsonCompatibilityTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/JsonCompatibilityTest.java
deleted file mode 100644
index 0377278..0000000
--- a/src/test/java/com/android/tools/r8/desugar/corelib/JsonCompatibilityTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (c) 2019, 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.desugar.corelib;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.DesugaredLibraryConfigurationForTesting;
-import com.android.tools.r8.StringResource;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Reporter;
-import java.nio.file.Paths;
-import java.util.Map;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class JsonCompatibilityTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
-  }
-
-  public JsonCompatibilityTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void testCompatibilityProgram() {
-    DexItemFactory factory = new DexItemFactory();
-    InternalOptions options1 = new InternalOptions(factory, new Reporter());
-    options1.minApiLevel = parameters.getApiLevel().getLevel();
-    options1.desugaredLibraryConfiguration =
-        DesugaredLibraryConfigurationForTesting.configureLibraryDesugaringForProgramCompilation(
-            parameters.getApiLevel().getLevel(), factory);
-
-    Reporter reporter = new Reporter();
-    InternalOptions options2 = new InternalOptions(factory, reporter);
-    options2.minApiLevel = parameters.getApiLevel().getLevel();
-    options2.desugaredLibraryConfiguration =
-        new DesugaredLibraryConfigurationParser(
-                factory, reporter, false, parameters.getApiLevel().getLevel())
-            .parse(
-                StringResource.fromFile(
-                    Paths.get(
-                        "src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json")));
-
-    assertConfigurationEquals(
-        options1.desugaredLibraryConfiguration, options2.desugaredLibraryConfiguration);
-  }
-
-  @Test
-  public void testCompatibilityLibrary() {
-    DexItemFactory factory = new DexItemFactory();
-    InternalOptions options1 = new InternalOptions(factory, new Reporter());
-    options1.minApiLevel = parameters.getApiLevel().getLevel();
-    options1.desugaredLibraryConfiguration =
-        DesugaredLibraryConfigurationForTesting.configureLibraryDesugaringForLibraryCompilation(
-            parameters.getApiLevel().getLevel(), factory);
-
-    Reporter reporter = new Reporter();
-    InternalOptions options2 = new InternalOptions(factory, reporter);
-    options2.minApiLevel = parameters.getApiLevel().getLevel();
-    options2.desugaredLibraryConfiguration =
-        new DesugaredLibraryConfigurationParser(
-                factory, reporter, true, parameters.getApiLevel().getLevel())
-            .parse(
-                StringResource.fromFile(
-                    Paths.get(
-                        "src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json")));
-
-    assertConfigurationEquals(
-        options1.desugaredLibraryConfiguration, options2.desugaredLibraryConfiguration);
-  }
-
-  private void assertConfigurationEquals(
-      DesugaredLibraryConfiguration libraryConfiguration1,
-      DesugaredLibraryConfiguration libraryConfiguration2) {
-    assertDictEquals(
-        libraryConfiguration1.getRewritePrefix(), libraryConfiguration2.getRewritePrefix());
-    assertDictEquals(
-        libraryConfiguration1.getEmulateLibraryInterface(),
-        libraryConfiguration2.getEmulateLibraryInterface());
-    assertDictEquals(
-        libraryConfiguration1.getBackportCoreLibraryMember(),
-        libraryConfiguration2.getBackportCoreLibraryMember());
-    assertRetargetEquals(
-        libraryConfiguration1.getRetargetCoreLibMember(),
-        libraryConfiguration2.getRetargetCoreLibMember());
-    assertEquals(
-        libraryConfiguration1.getDontRewriteInvocation().size(),
-        libraryConfiguration1.getDontRewriteInvocation().size());
-  }
-
-  private void assertRetargetEquals(
-      Map<DexString, Map<DexType, DexType>> retarget1,
-      Map<DexString, Map<DexType, DexType>> retarget2) {
-    assertEquals(retarget1.size(), retarget2.size());
-    for (DexString dexString : retarget1.keySet()) {
-      assert retarget2.containsKey(dexString);
-      assertDictEquals(retarget1.get(dexString), retarget2.get(dexString));
-    }
-  }
-
-  private <E> void assertDictEquals(Map<E, E> map1, Map<E, E> map2) {
-    assertEquals(map1.size(), map2.size());
-    for (E key : map1.keySet()) {
-      assertTrue(map2.containsKey(key) && map1.get(key).equals(map2.get(key)));
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTestBase.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTestBase.java
index eca2a47..e97834f 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTestBase.java
@@ -14,12 +14,17 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
+import org.junit.Assume;
 
 public class APIConversionTestBase extends CoreLibDesugarTestBase {
 
   private static final Path CONVERSION_FOLDER = Paths.get("src/test/desugaredLibraryConversions");
 
   public Path[] getTimeConversionClasses() throws IOException {
+    Assume.assumeTrue(
+        "JDK8 javac is required to avoid dealing with modules and JDK8 is not checked-in on"
+            + " windows",
+        !ToolHelper.isWindows());
     File conversionFolder = temp.newFolder("conversions");
     File stubsFolder = temp.newFolder("stubs");
 
@@ -28,7 +33,7 @@
         CfVm.JDK8,
         null,
         stubsFolder.toPath(),
-        getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("stubs/"), "java"));
+        getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("stubs"), "java"));
 
     // Compile the conversions using the stubs.
     ArrayList<Path> classPath = new ArrayList<>();
@@ -46,7 +51,7 @@
   }
 
   protected Path buildDesugaredLibraryWithConversionExtension(AndroidApiLevel apiLevel) {
-    Path[] timeConversionClasses = null;
+    Path[] timeConversionClasses;
     try {
       timeConversionClasses = getTimeConversionClasses();
     } catch (IOException e) {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
new file mode 100644
index 0000000..71e1a3f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, 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.desugar.corelib.conversionTests;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.MonthDay;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import org.junit.Test;
+
+public class AllTimeConversionTest extends APIConversionTestBase {
+
+  @Test
+  public void testRewrittenAPICalls() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+        .assertSuccessWithOutput(
+            StringUtils.lines(
+                "1970-01-02T00:00Z[GMT]",
+                "PT0.000012345S",
+                "GMT",
+                "--03-02",
+                "-1000000000-01-01T00:00:00.999999999Z",
+                "GMT",
+                "GMT"));
+  }
+
+  static class Executor {
+
+    private static final String ZONE_ID = "GMT";
+
+    public static void main(String[] args) {
+      returnValueUsed();
+      returnValueUnused();
+      virtualMethods();
+    }
+
+    public static void returnValueUsed() {
+      System.out.println(
+          CustomLibClass.mix(
+              ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID)),
+              ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID))));
+      CustomLibClass.mix(LocalDate.of(2000, 3, 13), LocalDate.of(1990, 5, 25));
+      System.out.println(CustomLibClass.mix(Duration.ZERO, Duration.ofNanos(12345)));
+      System.out.println(CustomLibClass.mix(ZoneId.of(ZONE_ID), ZoneId.of(ZONE_ID)));
+      System.out.println(CustomLibClass.mix(MonthDay.of(3, 4), MonthDay.of(1, 2)));
+      System.out.println(CustomLibClass.mix(Instant.MIN, Instant.MAX));
+    }
+
+    public static void returnValueUnused() {
+      CustomLibClass.mix(
+          ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID)),
+          ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID)));
+      CustomLibClass.mix(LocalDate.of(2000, 3, 13), LocalDate.of(1990, 5, 25));
+      CustomLibClass.mix(Duration.ZERO, Duration.ofNanos(12345));
+      CustomLibClass.mix(ZoneId.of(ZONE_ID), ZoneId.of(ZONE_ID));
+      CustomLibClass.mix(MonthDay.of(3, 4), MonthDay.of(1, 2));
+      CustomLibClass.mix(Instant.MIN, Instant.MAX);
+    }
+
+    public static void virtualMethods() {
+      ZoneId of = ZoneId.of(ZONE_ID);
+      CustomLibClass customLibClass = new CustomLibClass();
+      customLibClass.virtual(of);
+      customLibClass.virtualString(of);
+      System.out.println(customLibClass.virtual(of));
+      System.out.println(customLibClass.virtualString(of));
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. None of the methods make sense.
+  static class CustomLibClass {
+
+    public static ZonedDateTime mix(ZonedDateTime zonedDateTime1, ZonedDateTime zonedDateTime2) {
+      return zonedDateTime1.plusDays(zonedDateTime2.getDayOfMonth());
+    }
+
+    public static LocalDate mix(LocalDate localDate1, LocalDate localDate2) {
+      return localDate1.plusDays(localDate2.getDayOfYear());
+    }
+
+    public static Duration mix(Duration duration1, Duration duration2) {
+      return duration1.plus(duration2);
+    }
+
+    public static ZoneId mix(ZoneId zoneId1, ZoneId zoneId2) {
+      return zoneId1;
+    }
+
+    public static MonthDay mix(MonthDay monthDay1, MonthDay monthDay2) {
+      return monthDay1.withDayOfMonth(monthDay2.getDayOfMonth());
+    }
+
+    public static Instant mix(Instant instant1, Instant instant2) {
+      return instant1.plusNanos(instant2.getNano());
+    }
+
+    public ZoneId virtual(ZoneId zoneId) {
+      return zoneId;
+    }
+
+    public String virtualString(ZoneId zoneId) {
+      return zoneId.getId();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicTimeConversionTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java
rename to src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicTimeConversionTest.java
index 2cdf6e7..e82ae21 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicTimeConversionTest.java
@@ -25,7 +25,7 @@
 import java.util.TimeZone;
 import org.junit.Test;
 
-public class TimeConversionCompilationTest extends APIConversionTestBase {
+public class BasicTimeConversionTest extends APIConversionTestBase {
 
   @Test
   public void testTimeGeneratedDex() throws Exception {
@@ -53,7 +53,7 @@
   public void testRewrittenAPICalls() throws Exception {
     testForD8()
         .setMinApi(AndroidApiLevel.B)
-        .addInnerClasses(TimeConversionCompilationTest.class)
+        .addInnerClasses(BasicTimeConversionTest.class)
         .enableCoreLibraryDesugaring(AndroidApiLevel.B)
         .compile()
         .inspect(this::checkAPIRewritten)
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
index 5622177..37b03d6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.dexsplitter;
 
 import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
 import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DexIndexedConsumer;
@@ -18,10 +20,13 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.concurrent.ExecutionException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,7 +50,7 @@
 
   private static FeatureSplit emptySplitProvider(FeatureSplit.Builder builder) {
     builder
-        .addProgramResourceProvider(() -> ImmutableList.of())
+        .addProgramResourceProvider(ImmutableList::of)
         .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
     return builder.build();
   }
@@ -111,6 +116,74 @@
     assertEquals(markers.iterator().next(), feature2Markers.iterator().next());
   }
 
+  @Test
+  public void testNonJavaPassThrough() throws IOException, CompilationFailedException {
+    Path basePath = temp.newFile("base.zip").toPath();
+    Path feature1Path = temp.newFile("feature1.zip").toPath();
+    Path feature2Path = temp.newFile("feature2.zip").toPath();
+    Collection<String> nonJavaFiles = ImmutableList.of("foobar", "barfoo");
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
+        .setMinApi(parameters.getRuntime())
+        .addFeatureSplit(
+            builder ->
+                splitWithNonJavaFile(
+                    builder, feature1Path, temp, nonJavaFiles, true, FeatureClass.class))
+        .addFeatureSplit(
+            builder ->
+                splitWithNonJavaFile(
+                    builder, feature2Path, temp, nonJavaFiles, true, FeatureClass2.class))
+        .addKeepAllClassesRule()
+        .compile()
+        .writeToZip(basePath);
+    for (Path feature : ImmutableList.of(feature1Path, feature2Path)) {
+      ZipFile zipFile = new ZipFile(feature.toFile());
+      for (String nonJavaFile : nonJavaFiles) {
+        ZipEntry entry = zipFile.getEntry(nonJavaFile);
+        assertNotNull(entry);
+        String content = new String(ByteStreams.toByteArray(zipFile.getInputStream(entry)));
+        assertEquals(content, nonJavaFile);
+      }
+    }
+  }
+
+  @Test
+  public void testAdaptResourceContentInSplits() throws IOException, CompilationFailedException {
+    Path basePath = temp.newFile("base.zip").toPath();
+    Path feature1Path = temp.newFile("feature1.zip").toPath();
+    Path feature2Path = temp.newFile("feature2.zip").toPath();
+    // Make the content of the data resource be class names
+    Collection<String> nonJavaFiles =
+        ImmutableList.of(FeatureClass.class.getName(), FeatureClass2.class.getName());
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
+        .setMinApi(parameters.getRuntime())
+        .addFeatureSplit(
+            builder ->
+                splitWithNonJavaFile(
+                    builder, feature1Path, temp, nonJavaFiles, false, FeatureClass.class))
+        .addFeatureSplit(
+            builder ->
+                splitWithNonJavaFile(
+                    builder, feature2Path, temp, nonJavaFiles, false, FeatureClass2.class))
+        .addKeepClassRulesWithAllowObfuscation(
+            BaseClass.class, FeatureClass.class, FeatureClass2.class)
+        .addKeepRules("-adaptresourcefilecontents")
+        .compile()
+        .writeToZip(basePath);
+    for (Path feature : ImmutableList.of(feature1Path, feature2Path)) {
+      ZipFile zipFile = new ZipFile(feature.toFile());
+      for (String nonJavaFile : nonJavaFiles) {
+        ZipEntry entry = zipFile.getEntry(nonJavaFile);
+        assertNotNull(entry);
+        String content = new String(ByteStreams.toByteArray(zipFile.getInputStream(entry)));
+        assertNotEquals(content, nonJavaFile);
+      }
+    }
+  }
+
   public static class HelloWorld {
     public static void main(String[] args) {
       System.out.println("Hello world");
@@ -169,6 +242,7 @@
       return feature2Path;
     }
 
+
     public CompiledWithFeature invoke() throws IOException, CompilationFailedException {
       basePath = temp.newFile("base.zip").toPath();
       feature1Path = temp.newFile("feature1.zip").toPath();
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index b8154cc..fea2bce 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -3,7 +3,6 @@
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.ArchiveProgramResourceProvider;
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
@@ -18,11 +17,15 @@
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dexsplitter.DexSplitter.Options;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
 import dalvik.system.PathClassLoader;
 import java.io.IOException;
 import java.net.MalformedURLException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
@@ -31,6 +34,9 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
 import org.junit.rules.TemporaryFolder;
 
 public class SplitterTestBase extends TestBase {
@@ -45,32 +51,78 @@
       Path outputPath,
       TemporaryFolder temp,
       Collection<Class<?>> classes) {
+    addConsumers(builder, outputPath, temp, null, true, classes);
+    return builder.build();
+  }
+
+  private static void addConsumers(
+      FeatureSplit.Builder builder,
+      Path outputPath,
+      TemporaryFolder temp,
+      Collection<String> nonJavaResources,
+      boolean ensureClassesInOutput,
+      Collection<Class<?>> classes) {
     List<String> classNames = classes.stream().map(Class::getName).collect(Collectors.toList());
     Path featureJar;
     try {
-      featureJar = temp.newFile().toPath();
+      featureJar = temp.newFolder().toPath().resolve("feature.jar");
       writeClassesToJar(featureJar, classes);
+      if (nonJavaResources != null && nonJavaResources.size() > 0) {
+        // We can't simply append to an existing zip easily, just copy the entries and add what we
+        // need.
+        Path newFeatureJar = temp.newFolder().toPath().resolve("feature.jar");
+
+        ZipOutputStream outputStream = new ZipOutputStream(Files.newOutputStream(newFeatureJar));
+        ZipInputStream inputStream = new ZipInputStream(Files.newInputStream(featureJar));
+        ZipEntry next = inputStream.getNextEntry();
+        while (next != null) {
+          outputStream.putNextEntry(new ZipEntry(next.getName()));
+          outputStream.write(ByteStreams.toByteArray(inputStream));
+          outputStream.closeEntry();
+          next = inputStream.getNextEntry();
+        }
+
+        for (String nonJavaResource : nonJavaResources) {
+          ZipUtils.writeToZipStream(
+              outputStream, nonJavaResource, nonJavaResource.getBytes(), ZipEntry.STORED);
+        }
+        outputStream.close();
+        featureJar = newFeatureJar;
+      }
     } catch (IOException e) {
       assertTrue(false);
-      return null;
+      return;
     }
 
     builder
-        .addProgramResourceProvider(ArchiveProgramResourceProvider.fromArchive(featureJar))
+        .addProgramResourceProvider(ArchiveResourceProvider.fromArchive(featureJar, true))
         .setProgramConsumer(
-            new ArchiveConsumer(outputPath) {
+            new ArchiveConsumer(outputPath, true) {
               @Override
               public void accept(
                   int fileIndex,
                   ByteDataView data,
                   Set<String> descriptors,
                   DiagnosticsHandler handler) {
-                for (String descriptor : descriptors) {
-                  assertTrue(classNames.contains(DescriptorUtils.descriptorToJavaType(descriptor)));
+                if (ensureClassesInOutput) {
+                  for (String descriptor : descriptors) {
+                    assertTrue(
+                        classNames.contains(DescriptorUtils.descriptorToJavaType(descriptor)));
+                  }
                 }
                 super.accept(fileIndex, data, descriptors, handler);
               }
             });
+  }
+
+  protected static FeatureSplit splitWithNonJavaFile(
+      FeatureSplit.Builder builder,
+      Path outputPath,
+      TemporaryFolder temp,
+      Collection<String> nonJavaFiles,
+      boolean ensureClassesInOutput,
+      Class... classes) {
+    addConsumers(builder, outputPath, temp, nonJavaFiles, true, Arrays.asList(classes));
     return builder.build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
index 15d2803..4ffbe2b 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
@@ -47,6 +47,14 @@
     return result;
   }
 
+  public Path getReleaseApk() {
+    return Paths.get(base).resolve("YouTubeRelease.apk");
+  }
+
+  public Path getReleaseProguardMap() {
+    return Paths.get(base).resolve("YouTubeRelease_proguard.map");
+  }
+
   void runR8AndCheckVerification(CompilationMode mode, String input) throws Exception {
     runAndCheckVerification(
         CompilerUnderTest.R8, mode, base + APK, null, null, ImmutableList.of(base + input));
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1217TreeShakeJarVerificationTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
rename to src/test/java/com/android/tools/r8/internal/YouTubeV1217TreeShakeJarVerificationTest.java
index 8856521..09cfa48 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1217TreeShakeJarVerificationTest.java
@@ -14,9 +14,9 @@
 import java.nio.file.Path;
 import org.junit.Test;
 
-public class YouTubeTreeShakeJarVerificationTest extends YouTubeCompilationBase {
+public class YouTubeV1217TreeShakeJarVerificationTest extends YouTubeCompilationBase {
 
-  public YouTubeTreeShakeJarVerificationTest() {
+  public YouTubeV1217TreeShakeJarVerificationTest() {
     super(12, 17);
   }
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
new file mode 100644
index 0000000..95004d5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2019, 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.internal;
+
+import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class YouTubeV1419TreeShakeJarVerificationTest extends YouTubeCompilationBase {
+
+  private static final int MAX_SIZE = 27500000;
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  public YouTubeV1419TreeShakeJarVerificationTest(TestParameters parameters) {
+    super(14, 19);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/141603168): Enable this on the bots.
+    assumeTrue(isLocalDevelopment());
+    assumeTrue(shouldRunSlowTests());
+
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addKeepRuleFiles(getKeepRuleFiles())
+            .addOptionsModification(
+                options -> {
+                  assert !options.enableFieldBitAccessAnalysis;
+                  options.enableFieldBitAccessAnalysis = true;
+
+                  assert !options.enableGeneratedExtensionRegistryShrinking;
+                  options.enableGeneratedExtensionRegistryShrinking = true;
+
+                  assert !options.enableGeneratedMessageLiteShrinking;
+                  options.enableGeneratedMessageLiteShrinking = true;
+
+                  assert !options.enableStringSwitchConversion;
+                  options.enableStringSwitchConversion = true;
+                })
+            .allowUnusedProguardConfigurationRules()
+            .compile();
+
+    if (ToolHelper.isLocalDevelopment()) {
+      DexItemFactory dexItemFactory = new DexItemFactory();
+      ProtoApplicationStats original =
+          new ProtoApplicationStats(dexItemFactory, new CodeInspector(getProgramFiles()));
+      ProtoApplicationStats actual =
+          new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
+      ProtoApplicationStats baseline =
+          new ProtoApplicationStats(
+              dexItemFactory,
+              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+      System.out.println(actual.getStats(baseline));
+    }
+
+    int applicationSize = applicationSize(compileResult.app);
+    System.out.println(applicationSize);
+
+    assertTrue(
+        "Expected max size of " + MAX_SIZE + ", got " + applicationSize,
+        applicationSize < MAX_SIZE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 69dc44b..59e114e 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -9,10 +9,14 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.List;
@@ -68,65 +72,74 @@
   @Test
   public void test() throws Exception {
     CodeInspector inputInspector = new CodeInspector(PROGRAM_FILES);
-    testForR8(parameters.getBackend())
-        .addProgramFiles(PROGRAM_FILES)
-        .addKeepMainRule("proto2.TestClass")
-        .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
-        .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
-        // TODO(b/112437944): Attempt to prove that DEFAULT_INSTANCE is non-null, such that the
-        //  following "assumenotnull" rule can be omitted.
-        .addKeepRules(
-            "-assumenosideeffects class " + EXT_C + " {",
-            "  private static final " + EXT_C + " DEFAULT_INSTANCE return 1..42;",
-            "}")
-        .addOptionsModification(
-            options -> {
-              options.enableFieldBitAccessAnalysis = true;
-              options.enableGeneratedMessageLiteShrinking = true;
-              options.enableGeneratedExtensionRegistryShrinking = true;
-              options.enableStringSwitchConversion = true;
-            })
-        .allowAccessModification(allowAccessModification)
-        .allowUnusedProguardConfigurationRules()
-        .minification(enableMinification)
-        .setMinApi(parameters.getRuntime())
-        .compile()
-        .inspect(
-            outputInspector -> {
-              verifyMapAndRequiredFieldsAreKept(inputInspector, outputInspector);
-              verifyUnusedExtensionsAreRemoved(inputInspector, outputInspector);
-              verifyUnusedFieldsAreRemoved(inputInspector, outputInspector);
-              verifyUnusedHazzerBitFieldsAreRemoved(inputInspector, outputInspector);
-              verifyUnusedTypesAreRemoved(inputInspector, outputInspector);
-            })
-        .run(parameters.getRuntime(), "proto2.TestClass")
-        .assertSuccessWithOutputLines(
-            "--- roundtrip ---",
-            "true",
-            "123",
-            "asdf",
-            "9223372036854775807",
-            "qwerty",
-            "--- partiallyUsed_proto2 ---",
-            "true",
-            "42",
-            "--- usedViaHazzer ---",
-            "true",
-            "--- usedViaOneofCase ---",
-            "true",
-            "--- usesOnlyRepeatedFields ---",
-            "1",
-            "--- containsFlaggedOffField ---",
-            "0",
-            "--- hasFlaggedOffExtension ---",
-            "4",
-            "--- useOneExtension ---",
-            "42",
-            "--- keepMapAndRequiredFields ---",
-            "true",
-            "10",
-            "10",
-            "10");
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(PROGRAM_FILES)
+            .addKeepMainRule("proto2.TestClass")
+            .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+            .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
+            // TODO(b/112437944): Attempt to prove that DEFAULT_INSTANCE is non-null, such that the
+            //  following "assumenotnull" rule can be omitted.
+            .addKeepRules(
+                "-assumenosideeffects class " + EXT_C + " {",
+                "  private static final " + EXT_C + " DEFAULT_INSTANCE return 1..42;",
+                "}")
+            .addOptionsModification(
+                options -> {
+                  options.enableFieldBitAccessAnalysis = true;
+                  options.enableGeneratedMessageLiteShrinking = true;
+                  options.enableGeneratedExtensionRegistryShrinking = true;
+                  options.enableStringSwitchConversion = true;
+                })
+            .allowAccessModification(allowAccessModification)
+            .allowUnusedProguardConfigurationRules()
+            .minification(enableMinification)
+            .setMinApi(parameters.getRuntime())
+            .compile()
+            .inspect(
+                outputInspector -> {
+                  verifyMapAndRequiredFieldsAreKept(inputInspector, outputInspector);
+                  verifyUnusedExtensionsAreRemoved(inputInspector, outputInspector);
+                  verifyUnusedFieldsAreRemoved(inputInspector, outputInspector);
+                  verifyUnusedHazzerBitFieldsAreRemoved(inputInspector, outputInspector);
+                  verifyUnusedTypesAreRemoved(inputInspector, outputInspector);
+                })
+            .run(parameters.getRuntime(), "proto2.TestClass")
+            .assertSuccessWithOutputLines(
+                "--- roundtrip ---",
+                "true",
+                "123",
+                "asdf",
+                "9223372036854775807",
+                "qwerty",
+                "--- partiallyUsed_proto2 ---",
+                "true",
+                "42",
+                "--- usedViaHazzer ---",
+                "true",
+                "--- usedViaOneofCase ---",
+                "true",
+                "--- usesOnlyRepeatedFields ---",
+                "1",
+                "--- containsFlaggedOffField ---",
+                "0",
+                "--- hasFlaggedOffExtension ---",
+                "4",
+                "--- useOneExtension ---",
+                "42",
+                "--- keepMapAndRequiredFields ---",
+                "true",
+                "10",
+                "10",
+                "10");
+
+    if (ToolHelper.isLocalDevelopment()) {
+      DexItemFactory dexItemFactory = new DexItemFactory();
+      ProtoApplicationStats original = new ProtoApplicationStats(dexItemFactory, inputInspector);
+      ProtoApplicationStats actual =
+          new ProtoApplicationStats(dexItemFactory, result.inspector(), original);
+      System.out.println(actual.getStats());
+    }
   }
 
   private void verifyMapAndRequiredFieldsAreKept(
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/CollectionMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/CollectionMethods.java
index 0dd1a53..ae3aef1 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/CollectionMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/CollectionMethods.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
+import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -44,4 +45,10 @@
     }
     return Collections.unmodifiableMap(map);
   }
+
+  public static <K, V> Map.Entry<K, V> mapEntry(K key, V value) {
+    return new AbstractMap.SimpleImmutableEntry<>(
+        Objects.requireNonNull(key),
+        Objects.requireNonNull(value));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java
index 0c77830..1890534 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,7 +44,6 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .addOptionsModification(InternalOptions::enableCallSiteOptimizationInfoPropagation)
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java
index 0ee0af3..ce2fd2e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,7 +44,6 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .addOptionsModification(InternalOptions::enableCallSiteOptimizationInfoPropagation)
         .addOptionsModification(o -> {
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java
index 09dd632..98012dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -43,7 +42,6 @@
         .addKeepMainRule(MAIN)
         .enableMergeAnnotations()
         .enableInliningAnnotations()
-        .addOptionsModification(InternalOptions::enableCallSiteOptimizationInfoPropagation)
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java
index 27057c1..cd4852d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,7 +44,6 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .addOptionsModification(InternalOptions::enableCallSiteOptimizationInfoPropagation)
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A:Sub1", "B:Sub1")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
index 9eef5ac..1db3da7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -43,7 +42,6 @@
         .addKeepMainRule(MAIN)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .addOptionsModification(InternalOptions::enableCallSiteOptimizationInfoPropagation)
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
index ae8ef12..b838d12 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -43,7 +42,6 @@
         .addKeepMainRule(MAIN)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .addOptionsModification(InternalOptions::enableCallSiteOptimizationInfoPropagation)
         .addOptionsModification(o -> {
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
index 53c9f8f..f1081fc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,7 +41,6 @@
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
         .setMinApi(parameters.getRuntime())
-        .addOptionsModification(InternalOptions::enableCallSiteOptimizationInfoPropagation)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
index bc4d0a7..7d0838c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,7 +44,6 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .addOptionsModification(InternalOptions::enableCallSiteOptimizationInfoPropagation)
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A", "B")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
index 79ea92e..d55484d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
@@ -44,7 +44,6 @@
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
         .enableClassInliningAnnotations()
-        .noMinification()
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Input")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionAsArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionAsArgumentTest.java
new file mode 100644
index 0000000..a93e72b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionAsArgumentTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2019, 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.staticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CompanionAsArgumentTest extends TestBase {
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // TODO(b/112831361): support for class staticizer in CF backend.
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public CompanionAsArgumentTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(CompanionAsArgumentTest.class)
+        .addKeepMainRule(MAIN)
+        .enableInliningAnnotations()
+        .enableClassInliningAnnotations()
+        .setMinApi(parameters.getRuntime())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("Companion#foo(true)")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Check if the candidate is not staticized.
+    ClassSubject companion = inspector.clazz(Host.Companion.class);
+    assertThat(companion, isPresent());
+    MethodSubject foo = companion.uniqueMethodWithName("foo");
+    assertThat(foo, isPresent());
+    assertTrue(foo.streamInstructions().anyMatch(
+        i -> i.isInvokeVirtual()
+            && i.getMethod().toSourceString().contains("PrintStream.println")));
+
+    // Nothing migrated from Companion to Host.
+    ClassSubject host = inspector.clazz(Host.class);
+    assertThat(host, isPresent());
+    MethodSubject migrated_foo = host.uniqueMethodWithName("foo");
+    assertThat(migrated_foo, not(isPresent()));
+  }
+
+  @NeverClassInline
+  static class Host {
+    private static final Companion companion = new Companion();
+
+    static class Companion {
+      @NeverInline
+      public void foo(Object arg) {
+        System.out.println("Companion#foo(" + (arg != null) + ")");
+      }
+    }
+
+    @NeverInline
+    static void bar() {
+      // The target singleton is used as not only a receiver but also an argument.
+      companion.foo(companion);
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      Host.bar();
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index a044d3f..59aae43 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -7,10 +7,8 @@
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
@@ -35,7 +33,6 @@
     final String extraRules =
         keepMainMethod(mainClassName) + neverInlineMethod(mainClassName, testMethodSignature);
     runTest(FOLDER, mainClassName, extraRules,
-        InternalOptions::enableCallSiteOptimizationInfoPropagation,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
           ClassSubject clazz = checkClassIsKept(codeInspector, ex1.getClassName());
@@ -61,13 +58,13 @@
     final String extraRules =
         keepMainMethod(mainClassName) + neverInlineMethod(mainClassName, testMethodSignature);
     runTest(FOLDER, mainClassName, extraRules,
-        InternalOptions::enableCallSiteOptimizationInfoPropagation,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
           ClassSubject clazz = checkClassIsKept(codeInspector, ex2.getClassName());
 
           MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
-          long ifzCount = testMethod.streamInstructions().filter(InstructionSubject::isIfEqz).count();
+          long ifzCount =
+              testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count();
           long paramNullCheckCount =
               countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
           // ?: in aOrDefault
@@ -86,13 +83,13 @@
     final String extraRules =
         keepMainMethod(mainClassName) + neverInlineMethod(mainClassName, testMethodSignature);
     runTest(FOLDER, mainClassName, extraRules,
-        InternalOptions::enableCallSiteOptimizationInfoPropagation,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
           ClassSubject clazz = checkClassIsKept(codeInspector, ex3.getClassName());
 
           MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
-          long ifzCount = testMethod.streamInstructions().filter(InstructionSubject::isIfEqz).count();
+          long ifzCount =
+              testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count();
           // !! operator inside explicit null check should be gone.
           // One explicit null-check as well as 4 bar? accesses.
           assertEquals(5, ifzCount);
diff --git a/src/test/java/com/android/tools/r8/naming/ObfuscationPrimitiveNamesTest.java b/src/test/java/com/android/tools/r8/naming/ObfuscationPrimitiveNamesTest.java
new file mode 100644
index 0000000..176cad1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ObfuscationPrimitiveNamesTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2019, 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.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ObfuscationPrimitiveNamesTest extends TestBase {
+
+  private static List<String> FORBIDDEN_NAMES =
+      Lists.newArrayList("int", "long", "boolean", "float");
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.print("Hello World!");
+    }
+  }
+
+  public static class A {}
+
+  public static class B {}
+
+  public static class C {}
+
+  public static class D {}
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public ObfuscationPrimitiveNamesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNotHavingPrimitiveNames()
+      throws IOException, CompilationFailedException, ExecutionException {
+    Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+    FileUtils.writeTextFile(dictionary, FORBIDDEN_NAMES);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ObfuscationPrimitiveNamesTest.class)
+        .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
+        .addKeepAllClassesRuleWithAllowObfuscation()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getRuntime())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput("Hello World!")
+        .inspect(
+            inspector -> {
+              assertEquals(5, inspector.allClasses().size());
+              assertTrue(
+                  inspector.allClasses().stream()
+                      .noneMatch(c -> FORBIDDEN_NAMES.contains(c.getFinalName())));
+            });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
index 88a6e04..5cdbd87 100644
--- a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -137,15 +137,15 @@
 
     ClassSubject superInterface1 = inspector.clazz(B112452064SuperInterface1.class);
     assertThat(superInterface1, isRenamed());
-    MethodSubject foo = superInterface1.method("void", "foo", ImmutableList.of());
-    assertThat(foo, isRenamed());
+    MethodSubject foo = superInterface1.uniqueMethodWithName("foo");
+    assertThat(foo, not(isPresent()));
     ClassSubject superInterface2 = inspector.clazz(B112452064SuperInterface2.class);
     if (enableVerticalClassMerging) {
       assertThat(superInterface2, not(isPresent()));
     } else {
       assertThat(superInterface2, isRenamed());
     }
-    MethodSubject bar = superInterface1.method("void", "bar", ImmutableList.of());
+    MethodSubject bar = superInterface2.uniqueMethodWithName("bar");
     assertThat(bar, not(isPresent()));
     ClassSubject subInterface = inspector.clazz(B112452064SubInterface.class);
     if (enableUnusedInterfaceRemoval) {
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumevaluesWithMultipleTargetsTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumevaluesWithMultipleTargetsTest.java
index 555fc7c..f91de77 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumevaluesWithMultipleTargetsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumevaluesWithMultipleTargetsTest.java
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
@@ -16,7 +16,6 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Streams;
 import java.util.Collection;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -125,17 +124,17 @@
 
       MethodSubject mainMethod = main.mainMethod();
       assertThat(mainMethod, isPresent());
-      assertEquals(
-          0,
-          Streams.stream(mainMethod.iterateInstructions(
-              i -> i.isInvoke() && i.getMethod().name.toString().equals("m"))).count());
+      assertTrue(
+          mainMethod.streamInstructions().noneMatch(
+              i -> i.isInvoke() && i.getMethod().name.toString().equals("m")));
 
       MethodSubject testInvokeInterface = main.uniqueMethodWithName("testInvokeInterface");
       assertThat(testInvokeInterface, isPresent());
-      assertEquals(
-          1,
-          Streams.stream(testInvokeInterface.iterateInstructions(
-              i -> i.isInvoke() && i.getMethod().name.toString().equals("m"))).count());
+      // With call site optimizations, the dynamic type of the argument is accurate (Impl1),
+      // hence the accurate resolution of the method call, resulting in rule application.
+      assertTrue(
+          testInvokeInterface.streamInstructions().noneMatch(
+              i -> i.isInvoke() && i.getMethod().name.toString().equals("m")));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
index 8430bb6..88d3b65 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
@@ -47,7 +47,11 @@
   private static void abstractMethodRemains(CodeInspector inspector) {
     ClassSubject programClass = inspector.clazz("shaking17.AbstractProgramClass");
     Assert.assertTrue(programClass.isPresent());
-    Assert.assertTrue(
+    // With call site optimization, the dynamic type of the argument of Shaking#callTheMethod is
+    // SubClass, not AbstractProgramClass. Then, the resolution of the invocation is accurately
+    // referring to SubClass#abstractMethod only, i.e., AbstractProgramClass#abstractMethod is no
+    // longer live, hence shrunken.
+    Assert.assertFalse(
         programClass.method("int", "abstractMethod", Collections.emptyList()).isPresent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index f2a924d..f28f242 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -6,7 +6,6 @@
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -41,8 +40,7 @@
         TreeShaking18Test::unusedRemoved,
         null,
         null,
-        ImmutableList.of("src/test/examples/shaking18/keep-rules.txt"),
-        InternalOptions::enableCallSiteOptimizationInfoPropagation);
+        ImmutableList.of("src/test/examples/shaking18/keep-rules.txt"));
   }
 
   private static void unusedRemoved(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 2277036..d7e46b1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
@@ -203,6 +204,10 @@
     return dexClass;
   }
 
+  public ClassSubject getSuperClass() {
+    return codeInspector.clazz(dexClass.superType.toSourceString());
+  }
+
   @Override
   public AnnotationSubject annotation(String name) {
     // Ensure we don't check for annotations represented as attributes.
@@ -233,6 +238,10 @@
     }
   }
 
+  public DexType getOriginalDexType(DexItemFactory dexItemFactory) {
+    return dexItemFactory.createType(getOriginalDescriptor());
+  }
+
   @Override
   public String getFinalName() {
     return DescriptorUtils.descriptorToJavaType(getFinalDescriptor());
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index 0d63e38..a4d8210 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -97,6 +99,11 @@
     return memberNaming != null ? (FieldSignature) memberNaming.getOriginalSignature() : signature;
   }
 
+  public DexField getOriginalDexField(DexItemFactory dexItemFactory) {
+    FieldSignature fieldSignature = getOriginalSignature();
+    return fieldSignature.toDexField(dexItemFactory, clazz.getOriginalDexType(dexItemFactory));
+  }
+
   @Override
   public FieldSignature getFinalSignature() {
     return FieldSignature.fromDexField(dexField.field);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 03e2755..0466c4e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -21,6 +21,8 @@
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexDebugPositionState;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.MemberNaming;
@@ -29,6 +31,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.LocalVariableTable.LocalVariableTableEntry;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -174,11 +177,28 @@
         dexMethod.annotations, GenericSignatureParser::parseMethodSignature);
   }
 
+  public DexMethod getOriginalDexMethod(DexItemFactory dexItemFactory) {
+    MethodSignature methodSignature = getOriginalSignature();
+    if (methodSignature.isQualified()) {
+      methodSignature = methodSignature.toUnqualified();
+    }
+    return methodSignature.toDexMethod(
+        dexItemFactory, dexItemFactory.createType(clazz.getOriginalDescriptor()));
+  }
+
   @Override
   public String getFinalSignatureAttribute() {
     return codeInspector.getFinalSignatureAttribute(dexMethod.annotations);
   }
 
+  public Iterable<InstructionSubject> instructions() {
+    return instructions(Predicates.alwaysTrue());
+  }
+
+  public Iterable<InstructionSubject> instructions(Predicate<InstructionSubject> predicate) {
+    return () -> iterateInstructions(predicate);
+  }
+
   @Override
   public Iterator<InstructionSubject> iterateInstructions() {
     return codeInspector.createInstructionIterator(this);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java b/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java
new file mode 100644
index 0000000..4a0f63d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java
@@ -0,0 +1,234 @@
+// Copyright (c) 2019, 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.utils.codeinspector.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.function.Function;
+
+public class ProtoApplicationStats {
+
+  private static final String EXTENDABLE_MESSAGE_TYPE =
+      "com.google.protobuf.GeneratedMessageLite$ExtendableMessage";
+  private static final String EXTENSION_REGISTRY_LITE_TYPE =
+      "com.google.protobuf.ExtensionRegistryLite";
+  private static final String GENERATED_EXTENSION_TYPE =
+      "com.google.protobuf.GeneratedMessageLite$GeneratedExtension";
+  private static final String GENERATED_MESSAGE_LITE_TYPE =
+      "com.google.protobuf.GeneratedMessageLite";
+  private static final String GENERATED_MESSAGE_LITE_BUILDER_TYPE =
+      GENERATED_MESSAGE_LITE_TYPE + "$Builder";
+
+  abstract static class Stats {
+
+    static <T> String progress(T actual, T baseline, T original, Function<T, Set<?>> fn) {
+      StringBuilder builder = new StringBuilder();
+      if (original != null) {
+        builder.append(fn.apply(original).size()).append(" -> ");
+      }
+      Set<?> actualSet = fn.apply(actual);
+      builder.append(actualSet.size());
+      if (baseline != null) {
+        Set<?> baselineSet = fn.apply(baseline);
+        builder
+            .append(" (unfulfilled potential: ")
+            .append(Sets.difference(actualSet, baselineSet).size())
+            .append(", improvement over baseline: ")
+            .append(Sets.difference(baselineSet, actualSet).size())
+            .append(" )");
+      }
+      return builder.toString();
+    }
+  }
+
+  class EnumStats extends Stats {
+
+    final Set<DexType> enums = Sets.newIdentityHashSet();
+
+    String getStats(EnumStats baseline, EnumStats original) {
+      return StringUtils.lines(
+          "Enum stats:", "  # enums: " + progress(this, baseline, original, x -> x.enums));
+    }
+  }
+
+  class ProtoBuilderStats extends Stats {
+
+    final Set<DexType> builders = Sets.newIdentityHashSet();
+
+    String getStats(ProtoBuilderStats baseline, ProtoBuilderStats original) {
+      return StringUtils.lines(
+          "Proto builder stats:",
+          "  # builders: " + progress(this, baseline, original, x -> x.builders));
+    }
+  }
+
+  class ProtoMessageStats extends Stats {
+
+    final boolean extendable;
+
+    ProtoMessageStats(boolean extendable) {
+      this.extendable = extendable;
+    }
+
+    final Set<DexType> messages = Sets.newIdentityHashSet();
+    final Set<DexField> bitFields = Sets.newIdentityHashSet();
+    final Set<DexField> nonBitFields = Sets.newIdentityHashSet();
+
+    String getStats(ProtoMessageStats baseline, ProtoMessageStats original) {
+      return StringUtils.lines(
+          extendable ? "Extendable proto message stats" : "Proto message stats:",
+          "  # messages: " + progress(this, baseline, original, x -> x.messages),
+          "  # bit fields: " + progress(this, baseline, original, x -> x.bitFields),
+          "  # non-bit fields: " + progress(this, baseline, original, x -> x.nonBitFields));
+    }
+  }
+
+  class GeneratedExtensionRegistryStats extends Stats {
+
+    final Set<DexMethod> findLiteExtensionByNumberMethods = Sets.newIdentityHashSet();
+    final Set<DexType> retainedExtensions = Sets.newIdentityHashSet();
+    final Set<DexType> spuriouslyRetainedExtensions = Sets.newIdentityHashSet();
+
+    String getStats(
+        GeneratedExtensionRegistryStats baseline, GeneratedExtensionRegistryStats original) {
+      return StringUtils.lines(
+          "Generated extension registry stats:",
+          "  # findLiteExtensionByNumber() methods: "
+              + progress(this, baseline, original, x -> x.findLiteExtensionByNumberMethods),
+          "  # retained extensions: "
+              + progress(this, baseline, original, x -> x.retainedExtensions),
+          "  # spuriously retained extensions: " + spuriouslyRetainedExtensions.size());
+    }
+  }
+
+  private final DexItemFactory dexItemFactory;
+  private final CodeInspector inspector;
+  private final ProtoApplicationStats original;
+
+  private final EnumStats enumStats = new EnumStats();
+  private final ProtoMessageStats extendableProtoMessageStats = new ProtoMessageStats(true);
+  private final GeneratedExtensionRegistryStats generatedExtensionRegistryStats =
+      new GeneratedExtensionRegistryStats();
+  private final ProtoBuilderStats protoBuilderStats = new ProtoBuilderStats();
+  private final ProtoMessageStats protoMessageStats = new ProtoMessageStats(false);
+
+  public ProtoApplicationStats(DexItemFactory dexItemFactory, CodeInspector inspector) {
+    this(dexItemFactory, inspector, null);
+  }
+
+  public ProtoApplicationStats(
+      DexItemFactory dexItemFactory, CodeInspector inspector, ProtoApplicationStats original) {
+    this.dexItemFactory = dexItemFactory;
+    this.inspector = inspector;
+    this.original = original;
+    computeStats();
+  }
+
+  private void computeStats() {
+    for (FoundClassSubject classSubject : inspector.allClasses()) {
+      DexType originalType = classSubject.getOriginalDexType(dexItemFactory);
+      if (classSubject.getDexClass().isEnum()) {
+        enumStats.enums.add(originalType);
+      }
+
+      ClassSubject superClassSubject = classSubject.getSuperClass();
+      if (!superClassSubject.isPresent()) {
+        continue;
+      }
+
+      ProtoMessageStats messageStats = null;
+      switch (superClassSubject.getOriginalName()) {
+        case GENERATED_MESSAGE_LITE_TYPE:
+          messageStats = protoMessageStats;
+          break;
+
+        case EXTENDABLE_MESSAGE_TYPE:
+          messageStats = extendableProtoMessageStats;
+          break;
+
+        case GENERATED_MESSAGE_LITE_BUILDER_TYPE:
+          protoBuilderStats.builders.add(
+              dexItemFactory.createType(classSubject.getOriginalDescriptor()));
+          break;
+
+        case EXTENSION_REGISTRY_LITE_TYPE:
+          for (FoundMethodSubject methodSubject : classSubject.allMethods()) {
+            String originalMethodName = methodSubject.getOriginalName(false);
+            if (originalMethodName.startsWith("findLiteExtensionByNumber")) {
+              generatedExtensionRegistryStats.findLiteExtensionByNumberMethods.add(
+                  methodSubject.getOriginalDexMethod(dexItemFactory));
+
+              for (InstructionSubject instruction :
+                  methodSubject.instructions(InstructionSubject::isStaticGet)) {
+                DexField field = instruction.getField();
+                FoundClassSubject typeClassSubject =
+                    inspector.clazz(field.type.toSourceString()).asFoundClassSubject();
+                if (!typeClassSubject.getOriginalName().equals(GENERATED_EXTENSION_TYPE)) {
+                  continue;
+                }
+                FoundClassSubject extensionClassSubject =
+                    inspector.clazz(field.holder.toSourceString()).asFoundClassSubject();
+                generatedExtensionRegistryStats.retainedExtensions.add(
+                    extensionClassSubject.getOriginalDexType(dexItemFactory));
+              }
+            }
+          }
+          break;
+      }
+
+      if (messageStats != null) {
+        messageStats.messages.add(originalType);
+        for (FoundFieldSubject fieldSubject : classSubject.allInstanceFields()) {
+          String originalFieldName = fieldSubject.getOriginalName(false);
+          if (originalFieldName.startsWith("bitField")) {
+            messageStats.bitFields.add(fieldSubject.getOriginalDexField(dexItemFactory));
+          } else {
+            messageStats.nonBitFields.add(fieldSubject.getOriginalDexField(dexItemFactory));
+          }
+        }
+      }
+    }
+
+    if (original != null) {
+      for (DexType extensionType : original.generatedExtensionRegistryStats.retainedExtensions) {
+        if (!generatedExtensionRegistryStats.retainedExtensions.contains(extensionType)
+            && inspector.clazz(extensionType.toSourceString()).isPresent()) {
+          generatedExtensionRegistryStats.spuriouslyRetainedExtensions.add(extensionType);
+        }
+      }
+    }
+  }
+
+  public String getStats() {
+    return StringUtils.lines(
+        enumStats.getStats(null, original.enumStats),
+        protoMessageStats.getStats(null, original.protoMessageStats),
+        extendableProtoMessageStats.getStats(null, original.extendableProtoMessageStats),
+        protoBuilderStats.getStats(null, original.protoBuilderStats),
+        generatedExtensionRegistryStats.getStats(null, original.generatedExtensionRegistryStats));
+  }
+
+  public String getStats(ProtoApplicationStats baseline) {
+    return StringUtils.lines(
+        enumStats.getStats(baseline.enumStats, original.enumStats),
+        protoMessageStats.getStats(baseline.protoMessageStats, original.protoMessageStats),
+        extendableProtoMessageStats.getStats(
+            baseline.extendableProtoMessageStats, original.extendableProtoMessageStats),
+        protoBuilderStats.getStats(baseline.protoBuilderStats, original.protoBuilderStats),
+        generatedExtensionRegistryStats.getStats(
+            baseline.generatedExtensionRegistryStats, original.generatedExtensionRegistryStats));
+  }
+}
diff --git a/third_party/opensource_apps.tar.gz.sha1 b/third_party/opensource_apps.tar.gz.sha1
index 6bc1780..a6f2eb2 100644
--- a/third_party/opensource_apps.tar.gz.sha1
+++ b/third_party/opensource_apps.tar.gz.sha1
@@ -1 +1 @@
-bc2e91a9f03b33eeeec6e224c1bd73d1d15d7a39
\ No newline at end of file
+792d03aef34debed948aa021de6f77e3dd07b267
\ No newline at end of file
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 89d0da2..8b87f84 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -307,6 +307,39 @@
       ]
   }),
   Repo({
+      'name': 'Simple-Camera',
+      'url': 'https://github.com/jsjeon/Simple-Camera',
+      'revision': '451fe188ab123e6956413b42e89839b44c05ac14',
+      'apps': [
+          App({
+              'id': 'com.simplemobiletools.camera.pro',
+              'signed_apk_name': 'camera-release.apk'
+          })
+      ]
+  }),
+  Repo({
+      'name': 'Simple-File-Manager',
+      'url': 'https://github.com/jsjeon/Simple-File-Manager',
+      'revision': '282b57d9e73f4d250cc844d8d73fd223509a141e',
+      'apps': [
+          App({
+              'id': 'com.simplemobiletools.filemanager.pro',
+              'signed_apk_name': 'file-manager-release.apk'
+          })
+      ]
+  }),
+  Repo({
+      'name': 'Simple-Gallery',
+      'url': 'https://github.com/jsjeon/Simple-Gallery',
+      'revision': '679125601eee7e057dfdfecd7bea6c4a6ac73ef9',
+      'apps': [
+          App({
+              'id': 'com.simplemobiletools.gallery.pro',
+              'signed_apk_name': 'gallery-release.apk'
+          })
+      ]
+  }),
+  Repo({
       'name': 'sqldelight',
       'url': 'https://github.com/christofferqa/sqldelight.git',
       'revision': '2e67a1126b6df05e4119d1e3a432fde51d76cdc8',