Version 1.4.47

Cherry pick: Add service loader test
CL: https://r8-review.googlesource.com/34420

Cherry pick: Read services from META-INF/services/
CL: https://r8-review.googlesource.com/34469

Cherry pick: Add support for ServiceLoader usages in Enqueuer
CL: https://r8-review.googlesource.com/34470

Cherry pick: Renaming and elimination of services in META-INF/services/
CL: https://r8-review.googlesource.com/34474

Cherry pick: Update AndroidAppConsumers to avoid "zip file closed" issue
CL: https://r8-review.googlesource.com/34475

Cherry pick: Update AndroidAppConsumers to avoid "zip file closed" issue for CF
CL: https://r8-review.googlesource.com/34476

Cherry pick: Attempt to fix tests
CL: https://r8-review.googlesource.com/c/r8/+/34477

Bug: 124181030
Change-Id: I51ac9631902f35f176a5d2d89beb4af1b83dba77
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 364a794..e124571 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -18,6 +18,7 @@
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.List;
+import java.util.Set;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -150,7 +151,8 @@
       return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
     }
 
-    public static void writeResources(Path archive, List<ProgramResource> resources)
+    public static void writeResources(
+        Path archive, List<ProgramResource> resources, Set<DataEntryResource> dataResources)
         throws IOException, ResourceException {
       OpenOption[] options =
           new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
@@ -163,6 +165,11 @@
             byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
             ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
           }
+          for (DataEntryResource dataResource : dataResources) {
+            String entryName = dataResource.getName();
+            byte[] bytes = ByteStreams.toByteArray(closer.register(dataResource.getByteStream()));
+            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+          }
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 466a26f..2273c4d 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -204,6 +204,7 @@
 
       new ApplicationWriter(
               app,
+              null,
               options,
               marker == null ? null : ImmutableList.copyOf(markers),
               null,
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index b6bb4e0..6974d46 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -95,6 +95,7 @@
         ApplicationWriter writer =
             new ApplicationWriter(
                 app,
+                null,
                 options,
                 markers,
                 null,
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index e235e67..3ca4bf2 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -179,7 +179,8 @@
       outputBuilder.close(handler);
     }
 
-    public static void writeResources(Path archive, List<ProgramResource> resources)
+    public static void writeResources(
+        Path archive, List<ProgramResource> resources, Set<DataEntryResource> dataResources)
         throws IOException, ResourceException {
       OpenOption[] options =
           new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
@@ -191,6 +192,11 @@
             byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
             ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
           }
+          for (DataEntryResource dataResource : dataResources) {
+            String entryName = dataResource.getName();
+            byte[] bytes = ByteStreams.toByteArray(closer.register(dataResource.getByteStream()));
+            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
+          }
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index f78912f..f7e5649 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -95,6 +95,7 @@
         try {
           new ApplicationWriter(
                   featureApp,
+                  null,
                   options,
                   markers,
                   null,
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 049854e..37311e8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -11,7 +11,9 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.AppliedGraphLens;
 import com.android.tools.r8.graph.DexApplication;
@@ -182,6 +184,7 @@
   static void writeApplication(
       ExecutorService executorService,
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       String deadCode,
       GraphLense graphLense,
       NamingLens namingLens,
@@ -194,6 +197,7 @@
       if (options.isGeneratingClassFiles()) {
         new CfApplicationWriter(
                 application,
+                appView,
                 options,
                 deadCode,
                 graphLense,
@@ -204,6 +208,7 @@
       } else {
         new ApplicationWriter(
                 application,
+                appView,
                 options,
                 marker == null ? null : Collections.singletonList(marker),
                 deadCode,
@@ -259,6 +264,8 @@
       AppView<AppInfoWithSubtyping> appView =
           new AppView<>(
               new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
+      appView.setAppServices(AppServices.builder(appView).build());
+
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       RootSet rootSet;
       String proguardSeedsData = null;
@@ -479,6 +486,8 @@
             new EnumOrdinalMapCollector(appViewWithLiveness, options).run());
       }
 
+      appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLense()));
+
       timing.begin("Create IR");
       Set<DexCallSite> desugaredCallSites;
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
@@ -667,6 +676,7 @@
       writeApplication(
           executorService,
           application,
+          appView,
           application.deadCode,
           appView.graphLense(),
           namingLens,
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 40cfef9..a3b2a90 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.4.46";
+  public static final String LABEL = "1.4.47";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 60167c5..2b661d0 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -178,7 +178,7 @@
     InternalOptions options = new InternalOptions();
     AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
     ApplicationWriter writer =
-        new ApplicationWriter(app, options, null, null, null, null, null, null);
+        new ApplicationWriter(app, null, options, null, null, null, null, null, null);
     writer.write(executor);
     compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
   }
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 24e82a7..c485a47 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -17,6 +17,8 @@
 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.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -59,6 +61,7 @@
 public class ApplicationWriter {
 
   public final DexApplication application;
+  public final AppView<? extends AppInfo> appView;
   public final String deadCode;
   public final GraphLense graphLense;
   public final NamingLens namingLens;
@@ -127,6 +130,7 @@
 
   public ApplicationWriter(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       InternalOptions options,
       List<Marker> markers,
       String deadCode,
@@ -136,6 +140,7 @@
       ProguardMapSupplier proguardMapSupplier) {
     this(
         application,
+        appView,
         options,
         markers,
         deadCode,
@@ -148,6 +153,7 @@
 
   public ApplicationWriter(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       InternalOptions options,
       List<Marker> markers,
       String deadCode,
@@ -158,6 +164,7 @@
       DexIndexedConsumer consumer) {
     assert application != null;
     this.application = application;
+    this.appView = appView;
     assert options != null;
     this.options = options;
     if (markers != null && !markers.isEmpty()) {
@@ -284,6 +291,7 @@
       // Supply info to all additional resource consumers.
       supplyAdditionalConsumers(
           application,
+          appView,
           graphLense,
           namingLens,
           options,
@@ -297,6 +305,7 @@
 
   public static void supplyAdditionalConsumers(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       GraphLense graphLense,
       NamingLens namingLens,
       InternalOptions options,
@@ -338,7 +347,7 @@
           .collect(Collectors.toList());
 
       ResourceAdapter resourceAdapter =
-          new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+          new ResourceAdapter(appView, application.dexItemFactory, graphLense, namingLens, options);
       Set<String> generatedResourceNames = new HashSet<>();
 
       for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
@@ -356,6 +365,10 @@
 
                 @Override
                 public void visit(DataEntryResource file) {
+                  if (resourceAdapter.shouldBeDeleted(file)) {
+                    return;
+                  }
+
                   DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
                   if (generatedResourceNames.add(adapted.getName())) {
                     dataResourceConsumer.accept(adapted, options.reporter);
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index ae59576..630f25d 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -8,11 +8,15 @@
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.graph.AppView;
 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.graph.GraphLense;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardPathFilter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -27,16 +31,19 @@
 
 public class ResourceAdapter {
 
+  private final AppView<? extends AppInfo> appView;
   private final DexItemFactory dexItemFactory;
   private final GraphLense graphLense;
   private final NamingLens namingLense;
   private final InternalOptions options;
 
   public ResourceAdapter(
+      AppView<? extends AppInfo> appView,
       DexItemFactory dexItemFactory,
       GraphLense graphLense,
       NamingLens namingLense,
       InternalOptions options) {
+    this.appView = appView;
     this.dexItemFactory = dexItemFactory;
     this.graphLense = graphLense;
     this.namingLense = namingLense;
@@ -88,8 +95,40 @@
     return DataDirectoryResource.fromName(adaptDirectoryName(directory), directory.getOrigin());
   }
 
+  // Returns true for files in META-INF/services/ that are never used by the application.
+  public boolean shouldBeDeleted(DataEntryResource file) {
+    if (appView != null && appView.appInfo().hasLiveness()) {
+      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+      if (file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)) {
+        String serviceName = file.getName().substring(AppServices.SERVICE_DIRECTORY_NAME.length());
+        if (!DescriptorUtils.isValidJavaType(serviceName)) {
+          return false;
+        }
+
+        DexString serviceDescriptor =
+            dexItemFactory.lookupString(DescriptorUtils.javaTypeToDescriptor(serviceName));
+        if (serviceDescriptor == null) {
+          return false;
+        }
+
+        DexType serviceType = appView.dexItemFactory().lookupType(serviceDescriptor);
+        if (serviceType == null) {
+          return false;
+        }
+
+        DexType rewrittenServiceType = appView.graphLense().lookupType(serviceType);
+        assert appView.appServices().allServiceTypes().contains(rewrittenServiceType);
+        return !appInfo.instantiatedAppServices.contains(rewrittenServiceType);
+      }
+    }
+    return false;
+  }
+
   private String adaptFileName(DataEntryResource file) {
-    FileNameAdapter adapter = new FileNameAdapter(file.getName());
+    FileNameAdapter adapter =
+        file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)
+            ? new ServiceFileNameAdapter(file.getName())
+            : new DefaultFileNameAdapter(file.getName());
     if (adapter.run()) {
       return adapter.getResult();
     }
@@ -307,7 +346,7 @@
 
     private void outputRangeFromInput(int from, int toExclusive) {
       if (from < toExclusive) {
-        result.append(contents.substring(from, toExclusive));
+        result.append(contents, from, toExclusive);
       }
     }
 
@@ -362,8 +401,8 @@
     }
   }
 
-  private abstract class FileNameAdapterBase extends StringAdapter {
-    public FileNameAdapterBase(String filename) {
+  private abstract class FileNameAdapter extends StringAdapter {
+    public FileNameAdapter(String filename) {
       super(filename);
     }
 
@@ -391,8 +430,8 @@
     }
   }
 
-  private class FileNameAdapter extends FileNameAdapterBase {
-    public FileNameAdapter(String filename) {
+  private class DefaultFileNameAdapter extends FileNameAdapter {
+    public DefaultFileNameAdapter(String filename) {
       super(filename);
     }
 
@@ -402,7 +441,28 @@
     }
   }
 
-  private class DirectoryNameAdapter extends FileNameAdapterBase {
+  private class ServiceFileNameAdapter extends FileNameAdapter {
+    public ServiceFileNameAdapter(String filename) {
+      super(filename);
+    }
+
+    @Override
+    public char getClassNameSeparator() {
+      return '.';
+    }
+
+    @Override
+    public boolean allowRenamingOfPrefixes() {
+      return false;
+    }
+
+    @Override
+    public boolean isRenamingCandidate(int from, int toExclusive) {
+      return from == AppServices.SERVICE_DIRECTORY_NAME.length() && eof(toExclusive);
+    }
+  }
+
+  private class DirectoryNameAdapter extends FileNameAdapter {
     public DirectoryNameAdapter(String filename) {
       super(filename);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
new file mode 100644
index 0000000..ec22d85
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -0,0 +1,178 @@
+// 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.graph;
+
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** A description of the services and their implementations found in META-INF/services/. */
+public class AppServices {
+
+  public static final String SERVICE_DIRECTORY_NAME = "META-INF/services/";
+
+  private final AppView<? extends AppInfo> appView;
+
+  // Mapping from service types to service implementation types.
+  private final Map<DexType, Set<DexType>> services;
+
+  private AppServices(AppView<? extends AppInfo> appView, Map<DexType, Set<DexType>> services) {
+    this.appView = appView;
+    this.services = services;
+  }
+
+  public Set<DexType> allServiceTypes() {
+    assert verifyRewrittenWithLens();
+    return services.keySet();
+  }
+
+  public Set<DexType> serviceImplementationsFor(DexType serviceType) {
+    assert verifyRewrittenWithLens();
+    assert services.containsKey(serviceType);
+    Set<DexType> serviceImplementationTypes = services.get(serviceType);
+    if (serviceImplementationTypes == null) {
+      assert false
+          : "Unexpected attempt to get service implementations for non-service type `"
+              + serviceType.toSourceString()
+              + "`";
+      return ImmutableSet.of();
+    }
+    return serviceImplementationTypes;
+  }
+
+  public AppServices rewrittenWithLens(GraphLense graphLens) {
+    ImmutableMap.Builder<DexType, Set<DexType>> rewrittenServices = ImmutableMap.builder();
+    for (Entry<DexType, Set<DexType>> entry : services.entrySet()) {
+      DexType rewrittenServiceType = graphLens.lookupType(entry.getKey());
+      ImmutableSet.Builder<DexType> rewrittenServiceImplementationTypes = ImmutableSet.builder();
+      for (DexType serviceImplementationType : entry.getValue()) {
+        rewrittenServiceImplementationTypes.add(graphLens.lookupType(serviceImplementationType));
+      }
+      rewrittenServices.put(rewrittenServiceType, rewrittenServiceImplementationTypes.build());
+    }
+    return new AppServices(appView, rewrittenServices.build());
+  }
+
+  private boolean verifyRewrittenWithLens() {
+    for (Entry<DexType, Set<DexType>> entry : services.entrySet()) {
+      assert entry.getKey() == appView.graphLense().lookupType(entry.getKey());
+      for (DexType type : entry.getValue()) {
+        assert type == appView.graphLense().lookupType(type);
+      }
+    }
+    return true;
+  }
+
+  public static Builder builder(AppView<? extends AppInfo> appView) {
+    return new Builder(appView);
+  }
+
+  public static class Builder {
+
+    private final AppView<? extends AppInfo> appView;
+    private final Map<DexType, Set<DexType>> services = new IdentityHashMap<>();
+
+    private Builder(AppView<? extends AppInfo> appView) {
+      this.appView = appView;
+    }
+
+    public AppServices build() {
+      Iterable<ProgramResourceProvider> programResourceProviders =
+          appView.appInfo().app.programResourceProviders;
+      for (ProgramResourceProvider programResourceProvider : programResourceProviders) {
+        DataResourceProvider dataResourceProvider =
+            programResourceProvider.getDataResourceProvider();
+        if (dataResourceProvider != null) {
+          readServices(dataResourceProvider);
+        }
+      }
+      return new AppServices(appView, services);
+    }
+
+    private void readServices(DataResourceProvider dataResourceProvider) {
+      try {
+        dataResourceProvider.accept(new DataResourceProviderVisitor());
+      } catch (ResourceException e) {
+        throw new CompilationError(e.getMessage(), e);
+      }
+    }
+
+    private class DataResourceProviderVisitor implements Visitor {
+
+      @Override
+      public void visit(DataDirectoryResource directory) {
+        // Ignore.
+      }
+
+      @Override
+      public void visit(DataEntryResource file) {
+        try {
+          String name = file.getName();
+          if (name.startsWith(SERVICE_DIRECTORY_NAME)) {
+            String serviceName = name.substring(SERVICE_DIRECTORY_NAME.length());
+            if (DescriptorUtils.isValidJavaType(serviceName)) {
+              String serviceDescriptor = DescriptorUtils.javaTypeToDescriptor(serviceName);
+              DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
+              byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+              String contents = new String(bytes, Charset.defaultCharset());
+              services.put(
+                  serviceType, readServiceImplementationsForService(contents, file.getOrigin()));
+            }
+          }
+        } catch (IOException | ResourceException e) {
+          throw new CompilationError(e.getMessage(), e);
+        }
+      }
+
+      private Set<DexType> readServiceImplementationsForService(String contents, Origin origin) {
+        if (contents != null) {
+          return Arrays.stream(contents.split(System.lineSeparator()))
+              .map(String::trim)
+              .filter(line -> !line.isEmpty())
+              .filter(DescriptorUtils::isValidJavaType)
+              .map(DescriptorUtils::javaTypeToDescriptor)
+              .map(appView.dexItemFactory()::createType)
+              .filter(
+                  serviceImplementationType -> {
+                    if (!serviceImplementationType.isClassType()) {
+                      // Should never happen.
+                      appView
+                          .options()
+                          .reporter
+                          .warning(
+                              new StringDiagnostic(
+                                  "Unexpected service implementation found in META-INF/services/: `"
+                                      + serviceImplementationType.toSourceString()
+                                      + "`.",
+                                  origin));
+                      return false;
+                    }
+                    return true;
+                  })
+              .collect(Collectors.toSet());
+        }
+        return ImmutableSet.of();
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index e232cb9..084ad80 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -11,6 +11,7 @@
 public class AppView<T extends AppInfo> {
 
   private T appInfo;
+  private AppServices appServices;
   private final DexItemFactory dexItemFactory;
   private GraphLense graphLense;
   private final InternalOptions options;
@@ -31,6 +32,14 @@
     this.appInfo = appInfo;
   }
 
+  public AppServices appServices() {
+    return appServices;
+  }
+
+  public void setAppServices(AppServices appServices) {
+    this.appServices = appServices;
+  }
+
   public DexItemFactory dexItemFactory() {
     return dexItemFactory;
   }
@@ -86,6 +95,16 @@
     }
 
     @Override
+    public AppServices appServices() {
+      return AppView.this.appServices();
+    }
+
+    @Override
+    public void setAppServices(AppServices appServices) {
+      AppView.this.setAppServices(appServices);
+    }
+
+    @Override
     public DexItemFactory dexItemFactory() {
       return AppView.this.dexItemFactory();
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 7a33ba4..8bdf115 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -163,6 +163,7 @@
   public final DexString objectDescriptor = createString("Ljava/lang/Object;");
   public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
   public final DexString classDescriptor = createString("Ljava/lang/Class;");
+  public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
   public final DexString autoCloseableDescriptor = createString("Ljava/lang/AutoCloseable;");
   public final DexString classArrayDescriptor = createString("[Ljava/lang/Class;");
   public final DexString fieldDescriptor = createString("Ljava/lang/reflect/Field;");
@@ -178,6 +179,7 @@
   public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
 
   public final DexString npeDescriptor = createString("Ljava/lang/NullPointerException;");
+  public final DexString serviceLoaderDescriptor = createString("Ljava/util/ServiceLoader;");
 
   public final DexString intFieldUpdaterDescriptor =
       createString("Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;");
@@ -226,6 +228,7 @@
   public final DexType annotationType = createType(annotationDescriptor);
   public final DexType throwableType = createType(throwableDescriptor);
   public final DexType classType = createType(classDescriptor);
+  public final DexType classLoaderType = createType(classLoaderDescriptor);
   public final DexType autoCloseableType = createType(autoCloseableDescriptor);
 
   public final DexType stringBuilderType = createType(stringBuilderDescriptor);
@@ -236,6 +239,7 @@
   public final DexType methodTypeType = createType(methodTypeDescriptor);
 
   public final DexType npeType = createType(npeDescriptor);
+  public final DexType serviceLoaderType = createType(serviceLoaderDescriptor);
 
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
@@ -255,6 +259,7 @@
       new AtomicFieldUpdaterMethods();
   public final Kotlin kotlin;
   public final PolymorphicMethods polymorphicMethods = new PolymorphicMethods();
+  public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
 
   public final DexString twrCloseResourceMethodName = createString("$closeResource");
   public final DexProto twrCloseResourceMethodProto =
@@ -753,6 +758,32 @@
     }
   }
 
+  public class ServiceLoaderMethods {
+
+    public final DexMethod load;
+    public final DexMethod loadWithClassLoader;
+    public final DexMethod loadInstalled;
+
+    private ServiceLoaderMethods() {
+      DexString loadName = createString("load");
+      load = createMethod(serviceLoaderType, createProto(serviceLoaderType, classType), loadName);
+      loadWithClassLoader =
+          createMethod(
+              serviceLoaderType,
+              createProto(serviceLoaderType, classType, classLoaderType),
+              loadName);
+      loadInstalled =
+          createMethod(
+              serviceLoaderType,
+              createProto(serviceLoaderType, classType),
+              createString("loadInstalled"));
+    }
+
+    public boolean isLoadMethod(DexMethod method) {
+      return method == load || method == loadWithClassLoader || method == loadInstalled;
+    }
+  }
+
   private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
     assert item != null;
     assert !DexItemFactory.isInternalSentinel(item);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 14459fe..7aa3ace 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
@@ -41,7 +43,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.concurrent.ExecutorService;
@@ -62,6 +63,7 @@
   private static final boolean PRINT_CF = false;
 
   private final DexApplication application;
+  private final AppView<? extends AppInfo> appView;
   private final GraphLense graphLense;
   private final NamingLens namingLens;
   private final InternalOptions options;
@@ -72,6 +74,7 @@
 
   public CfApplicationWriter(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       InternalOptions options,
       String deadCode,
       GraphLense graphLense,
@@ -79,6 +82,7 @@
       String proguardSeedsData,
       ProguardMapSupplier proguardMapSupplier) {
     this.application = application;
+    this.appView = appView;
     this.graphLense = graphLense;
     this.namingLens = namingLens;
     this.options = options;
@@ -87,7 +91,7 @@
     this.proguardSeedsData = proguardSeedsData;
   }
 
-  public void write(ClassFileConsumer consumer, ExecutorService executor) throws IOException {
+  public void write(ClassFileConsumer consumer, ExecutorService executor) {
     application.timing.begin("CfApplicationWriter.write");
     try {
       writeApplication(consumer, executor);
@@ -96,8 +100,7 @@
     }
   }
 
-  private void writeApplication(ClassFileConsumer consumer, ExecutorService executor)
-      throws IOException {
+  private void writeApplication(ClassFileConsumer consumer, ExecutorService executor) {
     for (DexProgramClass clazz : application.classes()) {
       if (clazz.getSynthesizedFrom().isEmpty()) {
         writeClass(clazz, consumer);
@@ -107,6 +110,7 @@
     }
     ApplicationWriter.supplyAdditionalConsumers(
         application,
+        appView,
         graphLense,
         namingLens,
         options,
@@ -115,7 +119,7 @@
         proguardSeedsData);
   }
 
-  private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
+  private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) {
     ClassWriter writer = new ClassWriter(0);
     writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
     int version = getClassFileVersion(clazz);
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 a51ca83..076e021 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
@@ -220,6 +221,12 @@
       new SetWithReason<>(this::registerField);
 
   /**
+   * Set of service types (from META-INF/services/) that may have been instantiated reflectively via
+   * ServiceLoader.load() or ServiceLoader.loadInstalled().
+   */
+  private final Set<DexType> instantiatedAppServices = Sets.newIdentityHashSet();
+
+  /**
    * Set of interface types for which a lambda expression can be reached. These never have a single
    * interface implementation.
    */
@@ -488,6 +495,10 @@
       if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
         pendingReflectiveUses.add(currentMethod);
       }
+      // Handling of application services.
+      if (appInfo.dexItemFactory.serviceLoaderMethods.isLoadMethod(method)) {
+        pendingReflectiveUses.add(currentMethod);
+      }
       if (!registerItemWithTargetAndContext(staticInvokes, method, currentMethod)) {
         return false;
       }
@@ -1674,6 +1685,13 @@
             collectReachedFields(staticFields, this::tryLookupStaticField)));
   }
 
+  private void markClassAsInstantiatedWithReason(DexClass clazz, KeepReason reason) {
+    workList.add(Action.markInstantiated(clazz, reason));
+    if (clazz.hasDefaultInitializer()) {
+      workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+    }
+  }
+
   private void markClassAsInstantiatedWithCompatRule(DexClass clazz) {
     ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
     proguardCompatibilityWorkList.add(
@@ -1714,6 +1732,10 @@
       handleJavaLangEnumValueOf(method, invoke);
       return;
     }
+    if (appInfo.dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) {
+      handleServiceLoaderInvocation(method, invoke);
+      return;
+    }
     if (!isReflectionMethod(appInfo.dexItemFactory, invokedMethod)) {
       return;
     }
@@ -1783,6 +1805,55 @@
     }
   }
 
+  private void handleServiceLoaderInvocation(DexEncodedMethod method, InvokeMethod invoke) {
+    if (invoke.inValues().size() == 0) {
+      // Should never happen.
+      return;
+    }
+
+    Value argument = invoke.inValues().get(0).getAliasedValue();
+    if (!argument.isPhi() && argument.definition.isConstClass()) {
+      DexType serviceType = argument.definition.asConstClass().getValue();
+      if (!appView.appServices().allServiceTypes().contains(serviceType)) {
+        // Should never happen.
+        options.reporter.warning(
+            new StringDiagnostic(
+                "The type `"
+                    + serviceType.toSourceString()
+                    + "` is being passed to the method `"
+                    + invoke.getInvokedMethod()
+                    + "`, but could was found in `META-INF/services/`.",
+                appInfo.originFor(method.method.holder)));
+        return;
+      }
+
+      handleServiceInstantiation(serviceType, KeepReason.reflectiveUseIn(method));
+    } else {
+      KeepReason reason = KeepReason.reflectiveUseIn(method);
+      for (DexType serviceType : appView.appServices().allServiceTypes()) {
+        handleServiceInstantiation(serviceType, reason);
+      }
+    }
+  }
+
+  private void handleServiceInstantiation(DexType serviceType, KeepReason reason) {
+    instantiatedAppServices.add(serviceType);
+
+    Set<DexType> serviceImplementationTypes =
+        appView.appServices().serviceImplementationsFor(serviceType);
+    for (DexType serviceImplementationType : serviceImplementationTypes) {
+      if (!serviceImplementationType.isClassType()) {
+        // Should never happen.
+        continue;
+      }
+
+      DexClass serviceImplementationClass = appInfo.definitionFor(serviceImplementationType);
+      if (serviceImplementationClass != null && serviceImplementationClass.isProgramClass()) {
+        markClassAsInstantiatedWithReason(serviceImplementationClass, reason);
+      }
+    }
+  }
+
   private static class Action {
 
     final Kind kind;
@@ -1856,6 +1927,11 @@
      */
     final SortedSet<DexType> instantiatedAnnotations;
     /**
+     * Set of service types (from META-INF/services/) that may have been instantiated reflectively
+     * via ServiceLoader.load() or ServiceLoader.loadInstalled().
+     */
+    public final SortedSet<DexType> instantiatedAppServices;
+    /**
      * Set of types that are actually instantiated. These cannot be abstract.
      */
     final SortedSet<DexType> instantiatedTypes;
@@ -2015,6 +2091,9 @@
           PresortedComparable<DexType>::slowCompareTo, enqueuer.liveTypes);
       this.instantiatedAnnotations = ImmutableSortedSet.copyOf(
           PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedAnnotations);
+      this.instantiatedAppServices =
+          ImmutableSortedSet.copyOf(
+              PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedAppServices);
       this.instantiatedTypes = ImmutableSortedSet.copyOf(
           PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedTypes.getItems());
       this.instantiatedLambdas =
@@ -2073,6 +2152,7 @@
       super(application);
       this.liveTypes = previous.liveTypes;
       this.instantiatedAnnotations = previous.instantiatedAnnotations;
+      this.instantiatedAppServices = previous.instantiatedAppServices;
       this.instantiatedTypes = previous.instantiatedTypes;
       this.instantiatedLambdas = previous.instantiatedLambdas;
       this.targetedMethods = previous.targetedMethods;
@@ -2122,6 +2202,8 @@
       this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
       this.instantiatedAnnotations =
           rewriteItems(previous.instantiatedAnnotations, lense::lookupType);
+      this.instantiatedAppServices =
+          rewriteItems(previous.instantiatedAppServices, lense::lookupType);
       this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
       this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
       this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
@@ -2200,6 +2282,7 @@
       super(previous);
       this.liveTypes = previous.liveTypes;
       this.instantiatedAnnotations = previous.instantiatedAnnotations;
+      this.instantiatedAppServices = previous.instantiatedAppServices;
       this.instantiatedTypes = previous.instantiatedTypes;
       this.instantiatedLambdas = previous.instantiatedLambdas;
       this.targetedMethods = previous.targetedMethods;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index eb9916e..a767c86 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -57,9 +57,11 @@
     private Position keepParameterNamesOptionPosition;
     private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
     private final ProguardPathFilter.Builder adaptResourceFilenames =
-        ProguardPathFilter.builder().disable();
+        ProguardPathFilter.builder()
+            .addPattern(ProguardPathList.builder().addFileName("META-INF/services/*").build());
     private final ProguardPathFilter.Builder adaptResourceFileContents =
-        ProguardPathFilter.builder().disable();
+        ProguardPathFilter.builder()
+            .addPattern(ProguardPathList.builder().addFileName("META-INF/services/*").build());
     private final ProguardPathFilter.Builder keepDirectories =
         ProguardPathFilter.builder().disable();
     private boolean forceProguardCompatibility = false;
@@ -243,18 +245,10 @@
       adaptClassStrings.addPattern(pattern);
     }
 
-    public void enableAdaptResourceFilenames() {
-      adaptResourceFilenames.enable();
-    }
-
     public void addAdaptResourceFilenames(ProguardPathList pattern) {
       adaptResourceFilenames.addPattern(pattern);
     }
 
-    public void enableAdaptResourceFileContents() {
-      adaptResourceFileContents.enable();
-    }
-
     public void addAdaptResourceFileContents(ProguardPathList pattern) {
       adaptResourceFileContents.addPattern(pattern);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 709d27a..c2f4849 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -384,10 +384,8 @@
       } else if (acceptString("adaptclassstrings")) {
         parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
       } else if (acceptString("adaptresourcefilenames")) {
-        configurationBuilder.enableAdaptResourceFilenames();
         parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
       } else if (acceptString("adaptresourcefilecontents")) {
-        configurationBuilder.enableAdaptResourceFileContents();
         parsePathFilter(configurationBuilder::addAdaptResourceFileContents);
       } else if (acceptString("identifiernamestring")) {
         configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
@@ -1680,7 +1678,7 @@
       if (fileFilter == null) {
         throw parseError("Path filter expected");
       }
-      builder.addFileName(negated, fileFilter);
+      builder.addFileName(fileFilter, negated);
       skipWhitespace();
       while (acceptChar(',')) {
         skipWhitespace();
@@ -1690,7 +1688,7 @@
         if (fileFilter == null) {
           throw parseError("Path filter expected");
         }
-        builder.addFileName(negated, fileFilter);
+        builder.addFileName(fileFilter, negated);
         skipWhitespace();
       }
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java b/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
index 6766128..edcc4f6 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
@@ -26,7 +26,11 @@
     private Builder() {
     }
 
-    public Builder addFileName(boolean isNegated, String path) {
+    public Builder addFileName(String path) {
+      return addFileName(path, false);
+    }
+
+    public Builder addFileName(String path, boolean isNegated) {
       matchers.add(new FileNameMatcher(isNegated, path));
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 28d1628..4c15524 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResource;
 import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DirectoryClassFileProvider;
@@ -31,6 +32,7 @@
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
@@ -38,10 +40,12 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 /**
  * Collection of program files needed for processing.
@@ -213,6 +217,36 @@
     }
   }
 
+  public Set<DataEntryResource> getDataEntryResourcesForTesting() throws ResourceException {
+    Set<DataEntryResource> out = new TreeSet<>(Comparator.comparing(DataResource::getName));
+    for (ProgramResourceProvider programResourceProvider : getProgramResourceProviders()) {
+      DataResourceProvider dataResourceProvider = programResourceProvider.getDataResourceProvider();
+      if (dataResourceProvider != null) {
+        dataResourceProvider.accept(
+            new Visitor() {
+
+              @Override
+              public void visit(DataDirectoryResource directory) {
+                // Ignore.
+              }
+
+              @Override
+              public void visit(DataEntryResource file) {
+                try {
+                  byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+                  DataEntryResource copy =
+                      DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+                  out.add(copy);
+                } catch (IOException | ResourceException e) {
+                  throw new RuntimeException(e);
+                }
+              }
+            });
+      }
+    }
+    return out;
+  }
+
   /** Get program resource providers. */
   public List<ProgramResourceProvider> getProgramResourceProviders() {
     return programResourceProviders;
@@ -320,21 +354,19 @@
     }
   }
 
-  /**
-   * Write the dex program resources to @code{archive} and the proguard resource as its sibling.
-   */
+  /** Write the dex program resources to @code{archive}. */
   public void writeToZip(Path archive, OutputMode outputMode) throws IOException {
     try {
       if (outputMode == OutputMode.DexIndexed) {
-        List<ProgramResource> resources = getDexProgramResourcesForTesting();
-        DexIndexedConsumer.ArchiveConsumer.writeResources(archive, resources);
+        DexIndexedConsumer.ArchiveConsumer.writeResources(
+            archive, getDexProgramResourcesForTesting(), getDataEntryResourcesForTesting());
       } else if (outputMode == OutputMode.DexFilePerClassFile) {
         List<ProgramResource> resources = getDexProgramResourcesForTesting();
         DexFilePerClassFileConsumer.ArchiveConsumer.writeResources(
             archive, resources, programResourcesMainDescriptor);
       } else if (outputMode == OutputMode.ClassFile) {
-        List<ProgramResource> resources = getClassProgramResourcesForTesting();
-        ClassFileConsumer.ArchiveConsumer.writeResources(archive, resources);
+        ClassFileConsumer.ArchiveConsumer.writeResources(
+            archive, getClassProgramResourcesForTesting(), getDataEntryResourcesForTesting());
       } else {
         throw new Unreachable("Unsupported output-mode for writing: " + outputMode);
       }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
index 4371edd..b7beb82 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -6,15 +6,21 @@
 import com.android.tools.r8.BaseCompilerCommand;
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DexIndexedConsumer.ForwardingConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.origin.Origin;
+import com.google.common.io.ByteStreams;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -105,6 +111,44 @@
             }
           }
 
+          @Override
+          public DataResourceConsumer getDataResourceConsumer() {
+            DataResourceConsumer dataResourceConsumer =
+                consumer != null ? consumer.getDataResourceConsumer() : null;
+            return new DataResourceConsumer() {
+
+              @Override
+              public void accept(
+                  DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.accept(directory, diagnosticsHandler);
+                }
+              }
+
+              @Override
+              public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+                try {
+                  byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+                  DataEntryResource copy =
+                      DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+                  builder.addDataResource(copy);
+                  if (dataResourceConsumer != null) {
+                    dataResourceConsumer.accept(copy, diagnosticsHandler);
+                  }
+                } catch (IOException | ResourceException e) {
+                  throw new RuntimeException(e);
+                }
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.finished(handler);
+                }
+              }
+            };
+          }
+
           synchronized void addDexFile(int fileIndex, byte[] data, Set<String> descriptors) {
             files.put(fileIndex, new DescriptorsWithContents(descriptors, data));
           }
@@ -148,6 +192,44 @@
               assert getDataResourceConsumer() != null;
             }
           }
+
+          @Override
+          public DataResourceConsumer getDataResourceConsumer() {
+            DataResourceConsumer dataResourceConsumer =
+                consumer != null ? consumer.getDataResourceConsumer() : null;
+            return new DataResourceConsumer() {
+
+              @Override
+              public void accept(
+                  DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.accept(directory, diagnosticsHandler);
+                }
+              }
+
+              @Override
+              public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+                try {
+                  byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+                  DataEntryResource copy =
+                      DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+                  builder.addDataResource(copy);
+                  if (dataResourceConsumer != null) {
+                    dataResourceConsumer.accept(copy, diagnosticsHandler);
+                  }
+                } catch (IOException | ResourceException e) {
+                  throw new RuntimeException(e);
+                }
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.finished(handler);
+                }
+              }
+            };
+          }
         };
     programConsumer = wrapped;
     return wrapped;
@@ -182,6 +264,44 @@
               assert getDataResourceConsumer() != null;
             }
           }
+
+          @Override
+          public DataResourceConsumer getDataResourceConsumer() {
+            DataResourceConsumer dataResourceConsumer =
+                consumer != null ? consumer.getDataResourceConsumer() : null;
+            return new DataResourceConsumer() {
+
+              @Override
+              public void accept(
+                  DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.accept(directory, diagnosticsHandler);
+                }
+              }
+
+              @Override
+              public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+                try {
+                  byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+                  DataEntryResource copy =
+                      DataEntryResource.fromBytes(bytes, file.getName(), file.getOrigin());
+                  builder.addDataResource(copy);
+                  if (dataResourceConsumer != null) {
+                    dataResourceConsumer.accept(copy, diagnosticsHandler);
+                  }
+                } catch (IOException | ResourceException e) {
+                  throw new RuntimeException(e);
+                }
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                if (dataResourceConsumer != null) {
+                  dataResourceConsumer.finished(handler);
+                }
+              }
+            };
+          }
         };
     programConsumer = wrapped;
     return wrapped;
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 244639e..c91c288 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -141,7 +141,7 @@
     return builder.toString();
   }
 
-  public static String lines(String... lines) {
+  public static String lines(List<String> lines) {
     StringBuilder builder = new StringBuilder();
     for (String line : lines) {
       builder.append(line).append(LINE_SEPARATOR);
@@ -149,6 +149,10 @@
     return builder.toString();
   }
 
+  public static String lines(String... lines) {
+    return lines(Arrays.asList(lines));
+  }
+
   public static String withNativeLineSeparator(String s) {
     s = s.replace("\r\n", "\n");
     if (LINE_SEPARATOR.equals("\r\n")) {
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index e9d85be..7cbd89f 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -121,6 +121,11 @@
   }
 
   @Override
+  public ExternalR8TestBuilder addDataEntryResources(DataEntryResource... resources) {
+    throw new Unimplemented("No support for adding data entry resources");
+  }
+
+  @Override
   public ExternalR8TestBuilder addProgramClasses(Collection<Class<?>> classes) {
     // Adding a collection of classes will build a jar of exactly those classes so that no other
     // classes are made available via a too broad classpath directory.
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 0fd0354..3b4647f 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -181,6 +181,11 @@
   }
 
   @Override
+  public ProguardTestBuilder addDataEntryResources(DataEntryResource... resources) {
+    throw new Unimplemented("No support for adding data entry resources");
+  }
+
+  @Override
   public ProguardTestBuilder addProgramClasses(Collection<Class<?>> classes) {
     // Adding a collection of classes will build a jar of exactly those classes so that no other
     // classes are made available via a too broad classpath directory.
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index daf5568..e8555d7 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -97,6 +97,11 @@
   }
 
   @Override
+  public R8TestBuilder addDataEntryResources(DataEntryResource... resources) {
+    return addDataResources(Arrays.asList(resources));
+  }
+
+  @Override
   public R8TestBuilder addKeepRuleFiles(List<Path> files) {
     builder.addProguardConfigurationFiles(files);
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 9d8e03c..a17ab09 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -46,6 +46,8 @@
     return minification(false);
   }
 
+  public abstract T addDataEntryResources(DataEntryResource... resources);
+
   public abstract T addKeepRuleFiles(List<Path> files);
 
   public T addKeepRuleFiles(Path... files) throws IOException {
@@ -62,6 +64,10 @@
     return addKeepRules("-keep class ** { *; }");
   }
 
+  public T addKeepAllInterfacesRule() {
+    return addKeepRules("-keep interface ** { *; }");
+  }
+
   public T addKeepClassRules(Class<?>... classes) {
     for (Class<?> clazz : classes) {
       addKeepRules("-keep class " + clazz.getTypeName());
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f53b8e6..ea774b8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1774,6 +1774,7 @@
         Executors.newSingleThreadExecutor(),
         application,
         null,
+        null,
         GraphLense.getIdentityLense(),
         NamingLens.getIdentityLens(),
         null,
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 8d6953b..298ffe1 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -139,6 +139,7 @@
     ApplicationWriter writer =
         new ApplicationWriter(
             application,
+            null,
             options,
             null,
             null,
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index ff948aa..5128e00 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -777,6 +777,7 @@
     ApplicationWriter writer =
         new ApplicationWriter(
             application,
+            null,
             options,
             null,
             null,
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index 0f3accb..15599ef 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestCompileResult;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
@@ -60,13 +59,24 @@
     this.backend = backend;
   }
 
-  protected static class CustomDataResourceConsumer implements DataResourceConsumer {
+  public static class DataResourceConsumerForTesting implements DataResourceConsumer {
 
+    private final DataResourceConsumer inner;
     private final Map<String, ImmutableList<String>> resources = new HashMap<>();
 
+    public DataResourceConsumerForTesting() {
+      this(null);
+    }
+
+    public DataResourceConsumerForTesting(DataResourceConsumer inner) {
+      this.inner = inner;
+    }
+
     @Override
     public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
-      throw new Unreachable();
+      if (inner != null) {
+        inner.accept(directory, diagnosticsHandler);
+      }
     }
 
     @Override
@@ -79,6 +89,9 @@
       } catch (Exception e) {
         throw new RuntimeException(e);
       }
+      if (inner != null) {
+        inner.accept(file, diagnosticsHandler);
+      }
     }
 
     @Override
@@ -88,6 +101,10 @@
       return resources.get(name);
     }
 
+    public boolean isEmpty() {
+      return size() == 0;
+    }
+
     public int size() {
       return resources.size();
     }
@@ -175,7 +192,7 @@
   @Test
   public void testEnabled() throws Exception {
     String pgConf = getProguardConfigWithNeverInline(true, null);
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     CodeInspector inspector = compileWithR8(pgConf, dataResourceConsumer).inspector();
 
     // Check that the data resources have changed as expected.
@@ -257,7 +274,7 @@
 
   @Test
   public void testEnabledWithFilter() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(getProguardConfigWithNeverInline(true, "*.md"), dataResourceConsumer);
 
     // Check that the file matching the filter has changed as expected.
@@ -279,7 +296,7 @@
 
   @Test
   public void testDisabled() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
 
     // Check that all data resources are unchanged.
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 437f2dd..40f4249 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.naming.AdaptResourceFileContentsTest.CustomDataResourceConsumer;
+import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
@@ -107,7 +107,7 @@
 
   @Test
   public void testEnabled() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, null), dataResourceConsumer, this::checkR8Renamings);
     // Check that the generated resources have the expected names.
@@ -120,7 +120,7 @@
 
   @Test
   public void testEnabledWithFilter() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, "**.md"),
         dataResourceConsumer,
@@ -138,7 +138,7 @@
 
   @Test
   public void testDisabled() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
     // Check that none of the resources were renamed.
     for (DataEntryResource dataResource : getOriginalDataResources()) {
@@ -150,7 +150,7 @@
 
   @Test
   public void testCollisionBehavior() throws Exception {
-    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, null),
         dataResourceConsumer,
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
new file mode 100644
index 0000000..29e5bcd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -0,0 +1,174 @@
+// 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.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BooleanUtils;
+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.google.common.collect.Lists;
+import java.util.List;
+import java.util.ServiceLoader;
+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 ServiceLoaderTest extends TestBase {
+
+  private final Backend backend;
+  private final boolean includeWorldGreeter;
+
+  private DataResourceConsumerForTesting dataResourceConsumer;
+
+  @Parameters(name = "Backend: {0}, include WorldGreeter: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(Backend.values(), BooleanUtils.values());
+  }
+
+  public ServiceLoaderTest(Backend backend, boolean includeWorldGreeter) {
+    this.backend = backend;
+    this.includeWorldGreeter = includeWorldGreeter;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = includeWorldGreeter ? "Hello world!" : "Hello";
+
+    List<String> serviceImplementations = Lists.newArrayList(HelloGreeter.class.getTypeName());
+    if (includeWorldGreeter) {
+      serviceImplementations.add(WorldGreeter.class.getTypeName());
+    }
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(ServiceLoaderTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addDataEntryResources(
+                DataEntryResource.fromBytes(
+                    StringUtils.lines(serviceImplementations).getBytes(),
+                    "META-INF/services/" + Greeter.class.getTypeName(),
+                    Origin.unknown()))
+            .addOptionsModification(
+                options -> {
+                  dataResourceConsumer =
+                      new DataResourceConsumerForTesting(options.dataResourceConsumer);
+                  options.dataResourceConsumer = dataResourceConsumer;
+                })
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject greeterSubject = inspector.clazz(Greeter.class);
+    assertEquals(includeWorldGreeter, greeterSubject.isPresent());
+
+    ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
+    assertThat(helloGreeterSubject, isPresent());
+
+    ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
+    assertEquals(includeWorldGreeter, worldGreeterSubject.isPresent());
+
+    String serviceFileName =
+        includeWorldGreeter ? greeterSubject.getFinalName() : helloGreeterSubject.getFinalName();
+    List<String> lines =
+        dataResourceConsumer.get(AppServices.SERVICE_DIRECTORY_NAME + serviceFileName);
+    assertEquals(includeWorldGreeter ? 2 : 1, lines.size());
+    assertEquals(helloGreeterSubject.getFinalName(), lines.get(0));
+    if (includeWorldGreeter) {
+      assertEquals(worldGreeterSubject.getFinalName(), lines.get(1));
+    }
+
+    // TODO(b/124181030): Verify that -whyareyoukeeping works as intended.
+  }
+
+  @Test
+  public void testResourceElimination() throws Exception {
+    String expectedOutput = "Hello world!";
+
+    List<String> serviceImplementations = Lists.newArrayList(HelloGreeter.class.getTypeName());
+    if (includeWorldGreeter) {
+      serviceImplementations.add(WorldGreeter.class.getTypeName());
+    }
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(ServiceLoaderTest.class)
+            .addKeepMainRule(OtherTestClass.class)
+            .addDataEntryResources(
+                DataEntryResource.fromBytes(
+                    StringUtils.lines(serviceImplementations).getBytes(),
+                    "META-INF/services/" + Greeter.class.getTypeName(),
+                    Origin.unknown()))
+            .addOptionsModification(
+                options -> {
+                  dataResourceConsumer =
+                      new DataResourceConsumerForTesting(options.dataResourceConsumer);
+                  options.dataResourceConsumer = dataResourceConsumer;
+                })
+            .run(OtherTestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject greeterSubject = inspector.clazz(Greeter.class);
+    assertThat(greeterSubject, not(isPresent()));
+
+    ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
+    assertThat(helloGreeterSubject, not(isPresent()));
+
+    ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
+    assertThat(worldGreeterSubject, not(isPresent()));
+
+    assertTrue(dataResourceConsumer.isEmpty());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      for (Greeter greeter : ServiceLoader.load(Greeter.class)) {
+        System.out.print(greeter.greeting());
+      }
+    }
+  }
+
+  static class OtherTestClass {
+
+    public static void main(String[] args) {
+      System.out.print("Hello world!");
+    }
+  }
+
+  public interface Greeter {
+
+    String greeting();
+  }
+
+  public static class HelloGreeter implements Greeter {
+
+    @Override
+    public String greeting() {
+      return "Hello";
+    }
+  }
+
+  public static class WorldGreeter implements Greeter {
+
+    @Override
+    public String greeting() {
+      return " world!";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index fb61a57..b99cbdc 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -113,6 +113,7 @@
       ApplicationWriter writer =
           new ApplicationWriter(
               dexApp,
+              null,
               options,
               null,
               null,