Renaming and elimination of services in META-INF/services/

Bug: 124181030
Change-Id: Iab363008bffe5c257c9f48fba16e307f67bd586f
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 364a794..a557881 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -150,7 +150,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, List<DataEntryResource> dataResources)
         throws IOException, ResourceException {
       OpenOption[] options =
           new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
@@ -163,6 +164,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/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 27c564a..6826f63 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -96,6 +96,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 9c2a30c..4d79275 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -11,6 +11,7 @@
 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;
@@ -185,6 +186,7 @@
   static void writeApplication(
       ExecutorService executorService,
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       String deadCode,
       GraphLense graphLense,
       NamingLens namingLens,
@@ -198,6 +200,7 @@
       if (options.isGeneratingClassFiles()) {
         new CfApplicationWriter(
                 application,
+                appView,
                 options,
                 marker,
                 deadCode,
@@ -209,6 +212,7 @@
       } else {
         new ApplicationWriter(
                 application,
+                appView,
                 options,
                 Collections.singletonList(marker),
                 deadCode,
@@ -486,6 +490,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;
@@ -668,6 +674,7 @@
       writeApplication(
           executorService,
           application,
+          appView,
           application.deadCode,
           appView.graphLense(),
           namingLens,
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 26398d8..7b2c688 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;
@@ -60,6 +62,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;
@@ -129,6 +132,7 @@
 
   public ApplicationWriter(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       InternalOptions options,
       List<Marker> markers,
       String deadCode,
@@ -138,6 +142,7 @@
       ProguardMapSupplier proguardMapSupplier) {
     this(
         application,
+        appView,
         options,
         markers,
         deadCode,
@@ -150,6 +155,7 @@
 
   public ApplicationWriter(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       InternalOptions options,
       List<Marker> markers,
       String deadCode,
@@ -160,6 +166,7 @@
       DexIndexedConsumer consumer) {
     assert application != null;
     this.application = application;
+    this.appView = appView;
     assert options != null;
     this.options = options;
     this.markers = markers;
@@ -298,6 +305,7 @@
       // Supply info to all additional resource consumers.
       supplyAdditionalConsumers(
           application,
+          appView,
           graphLense,
           namingLens,
           options,
@@ -311,6 +319,7 @@
 
   public static void supplyAdditionalConsumers(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       GraphLense graphLense,
       NamingLens namingLens,
       InternalOptions options,
@@ -349,7 +358,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) {
@@ -367,6 +376,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
index 4c1881c..ec22d85 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -14,6 +14,7 @@
 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;
@@ -21,24 +22,32 @@
 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(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) {
@@ -51,14 +60,35 @@
     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 static final String SERVICE_DIRECTORY_NAME = "META-INF/services/";
-
     private final AppView<? extends AppInfo> appView;
     private final Map<DexType, Set<DexType>> services = new IdentityHashMap<>();
 
@@ -76,7 +106,7 @@
           readServices(dataResourceProvider);
         }
       }
-      return new AppServices(services);
+      return new AppServices(appView, services);
     }
 
     private void readServices(DataResourceProvider dataResourceProvider) {
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 8d55a59..6a9e056 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.dex.Marker;
 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;
@@ -42,7 +44,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;
@@ -67,6 +68,7 @@
   public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
 
   private final DexApplication application;
+  private final AppView<? extends AppInfo> appView;
   private final GraphLense graphLense;
   private final NamingLens namingLens;
   private final InternalOptions options;
@@ -78,6 +80,7 @@
 
   public CfApplicationWriter(
       DexApplication application,
+      AppView<? extends AppInfo> appView,
       InternalOptions options,
       Marker marker,
       String deadCode,
@@ -86,6 +89,7 @@
       String proguardSeedsData,
       ProguardMapSupplier proguardMapSupplier) {
     this.application = application;
+    this.appView = appView;
     this.graphLense = graphLense;
     this.namingLens = namingLens;
     this.options = options;
@@ -96,7 +100,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);
@@ -105,8 +109,7 @@
     }
   }
 
-  private void writeApplication(ClassFileConsumer consumer, ExecutorService executor)
-      throws IOException {
+  private void writeApplication(ClassFileConsumer consumer, ExecutorService executor) {
     ProguardMapSupplier.ProguardMapAndId proguardMapAndId = null;
     if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
       proguardMapAndId = proguardMapSupplier.getProguardMapAndId();
@@ -124,6 +127,7 @@
     }
     ApplicationWriter.supplyAdditionalConsumers(
         application,
+        appView,
         graphLense,
         namingLens,
         options,
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 f167226..b32134a 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -221,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.
    */
@@ -1845,6 +1851,8 @@
   }
 
   private void handleServiceInstantiation(DexType serviceType, KeepReason reason) {
+    instantiatedAppServices.add(serviceType);
+
     Set<DexType> serviceImplementationTypes =
         appView.appServices().serviceImplementationsFor(serviceType);
     for (DexType serviceImplementationType : serviceImplementationTypes) {
@@ -1931,6 +1939,11 @@
     /** Set of annotation types that are instantiated. */
     final SortedSet<DexType> instantiatedAnnotationTypes;
     /**
+     * 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;
@@ -2092,6 +2105,9 @@
           ImmutableSortedSet.orderedBy(PresortedComparable<DexType>::slowCompareTo);
       enqueuer.liveAnnotations.items.forEach(annotation -> builder.add(annotation.annotation.type));
       this.instantiatedAnnotationTypes = builder.build();
+      this.instantiatedAppServices =
+          ImmutableSortedSet.copyOf(
+              PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedAppServices);
       this.instantiatedTypes = ImmutableSortedSet.copyOf(
           PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedTypes.getItems());
       this.instantiatedLambdas =
@@ -2150,6 +2166,7 @@
       super(application);
       this.liveTypes = previous.liveTypes;
       this.instantiatedAnnotationTypes = previous.instantiatedAnnotationTypes;
+      this.instantiatedAppServices = previous.instantiatedAppServices;
       this.instantiatedTypes = previous.instantiatedTypes;
       this.instantiatedLambdas = previous.instantiatedLambdas;
       this.targetedMethods = previous.targetedMethods;
@@ -2199,6 +2216,8 @@
       this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
       this.instantiatedAnnotationTypes =
           rewriteItems(previous.instantiatedAnnotationTypes, 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);
@@ -2277,6 +2296,7 @@
       super(previous);
       this.liveTypes = previous.liveTypes;
       this.instantiatedAnnotationTypes = previous.instantiatedAnnotationTypes;
+      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/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 369367e..d22adac 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -355,8 +355,8 @@
         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 71ebb23..a855ab7 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -191,6 +191,29 @@
               assert getDataResourceConsumer() != null;
             }
           }
+
+          @Override
+          public DataResourceConsumer getDataResourceConsumer() {
+            assert consumer.getDataResourceConsumer() == null;
+            return new DataResourceConsumer() {
+
+              @Override
+              public void accept(
+                  DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+                // Ignore.
+              }
+
+              @Override
+              public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+                builder.addDataResource(file);
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                // Ignore.
+              }
+            };
+          }
         };
     programConsumer = wrapped;
     return wrapped;
@@ -225,6 +248,29 @@
               assert getDataResourceConsumer() != null;
             }
           }
+
+          @Override
+          public DataResourceConsumer getDataResourceConsumer() {
+            assert consumer.getDataResourceConsumer() == null;
+            return new DataResourceConsumer() {
+
+              @Override
+              public void accept(
+                  DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+                // Ignore.
+              }
+
+              @Override
+              public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+                builder.addDataResource(file);
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                // Ignore.
+              }
+            };
+          }
         };
     programConsumer = wrapped;
     return wrapped;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 4f80d5e..c0b06f4 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1784,6 +1784,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 5103951..30db56e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -779,6 +779,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 35b6b4c..15599ef 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -101,6 +101,10 @@
       return resources.get(name);
     }
 
+    public boolean isEmpty() {
+      return size() == 0;
+    }
+
     public int size() {
       return resources.size();
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index d856f1d..29e5bcd 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -5,11 +5,14 @@
 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;
@@ -27,16 +30,18 @@
 @RunWith(Parameterized.class)
 public class ServiceLoaderTest extends TestBase {
 
+  private final Backend backend;
   private final boolean includeWorldGreeter;
 
   private DataResourceConsumerForTesting dataResourceConsumer;
 
-  @Parameters(name = "Include WorldGreeter: {0}")
-  public static Boolean[] data() {
-    return BooleanUtils.values();
+  @Parameters(name = "Backend: {0}, include WorldGreeter: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(Backend.values(), BooleanUtils.values());
   }
 
-  public ServiceLoaderTest(boolean includeWorldGreeter) {
+  public ServiceLoaderTest(Backend backend, boolean includeWorldGreeter) {
+    this.backend = backend;
     this.includeWorldGreeter = includeWorldGreeter;
   }
 
@@ -50,12 +55,9 @@
     }
 
     CodeInspector inspector =
-        testForR8(Backend.DEX)
+        testForR8(backend)
             .addInnerClasses(ServiceLoaderTest.class)
             .addKeepMainRule(TestClass.class)
-            // TODO(b/124181030): It should not be necessary to keep Greeter, but the resource
-            //  adapter needs to rewrite the resource file names.
-            .addKeepRules("-keep interface " + Greeter.class.getTypeName())
             .addDataEntryResources(
                 DataEntryResource.fromBytes(
                     StringUtils.lines(serviceImplementations).getBytes(),
@@ -72,9 +74,7 @@
             .inspector();
 
     ClassSubject greeterSubject = inspector.clazz(Greeter.class);
-    // TODO(b/124181030): Greeter should be merged into HelloGreeter when the keep rule above is
-    //  removed.
-    assertThat(greeterSubject, isPresent());
+    assertEquals(includeWorldGreeter, greeterSubject.isPresent());
 
     ClassSubject helloGreeterSubject = inspector.clazz(HelloGreeter.class);
     assertThat(helloGreeterSubject, isPresent());
@@ -82,22 +82,59 @@
     ClassSubject worldGreeterSubject = inspector.clazz(WorldGreeter.class);
     assertEquals(includeWorldGreeter, worldGreeterSubject.isPresent());
 
-    // TODO(b/124181030): The resource file name should become:
-    //  `includeWorldGreeter ? greeterSubject.getFinalName() : helloGreeterSubject.getFinalName()`.
+    String serviceFileName =
+        includeWorldGreeter ? greeterSubject.getFinalName() : helloGreeterSubject.getFinalName();
     List<String> lines =
-        dataResourceConsumer.get("META-INF/services/" + greeterSubject.getOriginalName());
+        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 META-INF/services/...Greeter is removed if there is no call to
-    //  ServiceLoader.load().
-
     // 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) {
@@ -107,6 +144,13 @@
     }
   }
 
+  static class OtherTestClass {
+
+    public static void main(String[] args) {
+      System.out.print("Hello world!");
+    }
+  }
+
   public interface Greeter {
 
     String greeting();
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,