Merge commit 'bc75ee8beb893ce438f76b6fae6eafc31cbfec8c' into dev-release

Change-Id: Ie3d1c549c2c849128721c090695b4979b95341b5
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
index 909ab68..df95ae9 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
@@ -177,7 +177,7 @@
         test.maxParallelForks = processors.div(userDefinedCoresPerFork.toInt())
       } else {
         // On work machines this seems to give the best test execution time (without freezing).
-        test.maxParallelForks = maxOf(processors.div(3), 1)
+        test.maxParallelForks = maxOf(processors.div(8), 1)
         // On low cpu count machines (bots) we under subscribe, so increase the count.
         if (processors == 32) {
           test.maxParallelForks = 15
diff --git a/d8_r8/test_modules/testbase/build.gradle.kts b/d8_r8/test_modules/testbase/build.gradle.kts
index 336033b..cf20c77 100644
--- a/d8_r8/test_modules/testbase/build.gradle.kts
+++ b/d8_r8/test_modules/testbase/build.gradle.kts
@@ -110,6 +110,7 @@
     dependsOn(gradle.includedBuild("shared").task(":downloadDeps"))
     dependsOn(gradle.includedBuild("keepanno").task(":jar"))
     dependsOn(gradle.includedBuild("resourceshrinker").task(":jar"))
+    dependsOn(gradle.includedBuild("resourceshrinker").task(":depsJar"))
     from(testDependencies().map(::zipTree))
     from(resourceShrinkerDepsJarTask.outputs.getFiles().map(::zipTree))
     from(keepAnnoJarTask.outputs.getFiles().map(::zipTree))
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index ae366a3..ee5ff59 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -308,7 +308,7 @@
 
       appView.setArtProfileCollection(
           appView.getArtProfileCollection().withoutMissingItems(appView));
-      assert appView.getStartupProfile().isEmpty();
+      appView.setStartupProfile(appView.getStartupProfile().withoutMissingItems(appView));
 
       finalizeApplication(appView, executor, timing);
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index eee535b..42672c6 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1575,6 +1575,7 @@
         .setForceProguardCompatibility(forceProguardCompatibility)
         .setFeatureSplitConfiguration(featureSplitConfiguration)
         .setAndroidResourceProvider(androidResourceProvider)
+        .setPartialCompilationConfiguration(partialCompilationConfiguration)
         .setProguardConfiguration(proguardConfiguration)
         .setMainDexKeepRules(mainDexKeepRules)
         .setDesugaredLibraryConfiguration(desugaredLibrarySpecification)
diff --git a/src/main/java/com/android/tools/r8/R8Partial.java b/src/main/java/com/android/tools/r8/R8Partial.java
index eccfd59..02c612c 100644
--- a/src/main/java/com/android/tools/r8/R8Partial.java
+++ b/src/main/java/com/android/tools/r8/R8Partial.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.partial.R8PartialProgramPartioning;
 import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration.R8PartialD8SubCompilationConfiguration;
 import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration.R8PartialR8SubCompilationConfiguration;
+import com.android.tools.r8.profile.art.ArtProfileOptions;
+import com.android.tools.r8.profile.startup.StartupOptions;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FeatureSplitConsumers;
@@ -55,13 +57,6 @@
   }
 
   void runInternal(AndroidApp app, ExecutorService executor) throws IOException, ResourceException {
-    if (!options.getArtProfileOptions().getArtProfileProviders().isEmpty()) {
-      throw options.reporter.fatalError(
-          "Partial shrinking does not support baseline profile rewriting");
-    }
-    if (!options.getStartupOptions().getStartupProfileProviders().isEmpty()) {
-      throw options.reporter.fatalError("Partial shrinking does not support startup profiles");
-    }
     if (options.getProguardConfiguration().isProtoShrinkingEnabled()) {
       throw options.reporter.fatalError("Partial shrinking does not support proto shrinking");
     }
@@ -90,9 +85,8 @@
 
   private R8PartialInput runProcessInputStep(AndroidApp androidApp, ExecutorService executor)
       throws IOException {
-    // TODO(b/388421578): Add support for generating R8 partial compile dumps.
     DirectMappedDexApplication app =
-        new ApplicationReader(androidApp, options, timing).readWithoutDumping(executor).toDirect();
+        new ApplicationReader(androidApp, options, timing).read(executor).toDirect();
     R8PartialProgramPartioning partioning = R8PartialProgramPartioning.create(app);
     return new R8PartialInput(
         partioning.getD8Classes(),
@@ -120,13 +114,18 @@
     options.partialCompilationConfiguration.d8DexOptionsConsumer.accept(d8Options);
     R8PartialD8SubCompilationConfiguration subCompilationConfiguration =
         new R8PartialD8SubCompilationConfiguration(input.getD8Types(), input.getR8Types(), timing);
+    d8Options.setArtProfileOptions(
+        new ArtProfileOptions(d8Options, options.getArtProfileOptions()));
     d8Options.setFeatureSplitConfiguration(options.getFeatureSplitConfiguration());
+    d8Options.setStartupOptions(new StartupOptions(d8Options, options.getStartupOptions()));
     d8Options.partialSubCompilationConfiguration = subCompilationConfiguration;
     D8.runInternal(d8App, d8Options, executor);
     return new R8PartialD8Result(
+        subCompilationConfiguration.getArtProfiles(),
         subCompilationConfiguration.getClassToFeatureSplitMap(),
         subCompilationConfiguration.getDexedOutputClasses(),
-        subCompilationConfiguration.getDesugaredOutputClasses());
+        subCompilationConfiguration.getDesugaredOutputClasses(),
+        subCompilationConfiguration.getStartupProfile());
   }
 
   private void runR8Step(
@@ -191,8 +190,15 @@
     options.partialCompilationConfiguration.r8OptionsConsumer.accept(r8Options);
     r8Options.partialSubCompilationConfiguration =
         new R8PartialR8SubCompilationConfiguration(
-            d8Result.getClassToFeatureSplitMap(), d8Result.getDexedClasses(), timing);
+            d8Result.getArtProfiles(),
+            d8Result.getClassToFeatureSplitMap(),
+            d8Result.getDexedClasses(),
+            d8Result.getStartupProfile(),
+            timing);
+    r8Options.setArtProfileOptions(
+        new ArtProfileOptions(r8Options, options.getArtProfileOptions()));
     r8Options.setFeatureSplitConfiguration(options.getFeatureSplitConfiguration());
+    r8Options.setStartupOptions(new StartupOptions(r8Options, options.getStartupOptions()));
     r8Options.mapConsumer = options.mapConsumer;
     if (options.androidResourceProvider != null) {
       r8Options.androidResourceProvider = options.androidResourceProvider;
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index d7a0d7d..d46877f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -164,7 +164,9 @@
 
   private void dumpApplication(DumpInputFlags dumpInputFlags) {
     DumpOptions dumpOptions = options.dumpOptions;
-    if (dumpOptions == null || !dumpInputFlags.shouldDump(dumpOptions)) {
+    if (dumpOptions == null
+        || options.partialSubCompilationConfiguration != null
+        || !dumpInputFlags.shouldDump(dumpOptions)) {
       return;
     }
     Path dumpOutput = dumpInputFlags.getDumpPath();
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 a6cbf2e..c54b9ca 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -53,6 +53,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration.R8PartialR8SubCompilationConfiguration;
 import com.android.tools.r8.profile.startup.StartupCompleteness;
 import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.shaking.MainDexInfo;
@@ -67,6 +68,7 @@
 import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsDexPerFileConsumer;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.OriginalSourceFiles;
 import com.android.tools.r8.utils.PredicateUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -84,8 +86,8 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -94,6 +96,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -247,18 +250,12 @@
         && options.enableMainDexListCheck) {
       distributor = new VirtualFile.MonoDexDistributor(this, classes, options);
     } else {
-      // Retrieve the startup order for writing the app. In R8, the startup order is created
-      // up-front to guide optimizations through-out the compilation. In D8, the startup
-      // order is only used for writing the app, so we create it here for the first time.
-      StartupProfile startupProfile;
-      if (options.getStartupOptions().isStartupLayoutOptimizationEnabled()) {
-        startupProfile =
-            appView.appInfo().hasClassHierarchy()
-                ? appView.getStartupProfile()
-                : StartupProfile.createInitialStartupProfileForD8(appView);
-      } else {
-        startupProfile = StartupProfile.empty();
-      }
+      // Retrieve the startup order for writing the app. Use an empty startup profile if the startup
+      // profile should not be used for layout.
+      StartupProfile startupProfile =
+          options.getStartupOptions().isStartupLayoutOptimizationEnabled()
+              ? appView.getStartupProfile()
+              : StartupProfile.empty();
       distributor =
           new VirtualFile.FillFilesDistributor(
               this, classes, options, executorService, startupProfile);
@@ -495,31 +492,43 @@
       return OriginalSourceFiles.fromClasses();
     }
     if (!willComputeProguardMap()) {
-      rewriteSourceFile(null);
+      rewriteSourceFile(appView.appInfo().classes(), null);
       return OriginalSourceFiles.unreachable();
     }
     // Clear all source files so as not to collect the original files.
-    Collection<DexProgramClass> classes = appView.appInfo().classes();
-    Map<DexType, DexString> originalSourceFiles = new HashMap<>(classes.size());
-    for (DexProgramClass clazz : classes) {
-      DexString originalSourceFile = clazz.getSourceFile();
-      if (originalSourceFile != null) {
-        originalSourceFiles.put(clazz.getType(), originalSourceFile);
-        clazz.setSourceFile(null);
-      }
-    }
+    Map<DexProgramClass, DexString> originalSourceFiles = computeOriginalSourceFiles();
     // Add a lazy dex string computation to defer construction of the actual string.
     lazyDexStrings.add(
         new LazyDexString() {
           @Override
           public DexString internalCompute() {
-            return rewriteSourceFile(delayedProguardMapId.get());
+            return rewriteSourceFile(originalSourceFiles.keySet(), delayedProguardMapId.get());
           }
         });
-
     return OriginalSourceFiles.fromMap(originalSourceFiles);
   }
 
+  private Map<DexProgramClass, DexString> computeOriginalSourceFiles() {
+    Collection<DexProgramClass> classes = appView.appInfo().classes();
+    if (options.partialSubCompilationConfiguration == null) {
+      return MapUtils.transform(
+          classes,
+          IdentityHashMap::new,
+          Function.identity(),
+          DexProgramClass::getAndClearSourceFile);
+    } else {
+      R8PartialR8SubCompilationConfiguration subCompilationConfiguration =
+          options.partialSubCompilationConfiguration.asR8();
+      return MapUtils.transform(
+          classes,
+          ignore ->
+              new IdentityHashMap<>(
+                  classes.size() - subCompilationConfiguration.getDexingOutputClasses().size()),
+          clazz -> subCompilationConfiguration.isD8Definition(clazz) ? null : clazz,
+          DexProgramClass::getAndClearSourceFile);
+    }
+  }
+
   public static SourceFileEnvironment createSourceFileEnvironment(ProguardMapId proguardMapId) {
     if (proguardMapId == null) {
       return new SourceFileEnvironment() {
@@ -547,13 +556,14 @@
     };
   }
 
-  private DexString rewriteSourceFile(ProguardMapId proguardMapId) {
+  private DexString rewriteSourceFile(
+      Collection<DexProgramClass> classes, ProguardMapId proguardMapId) {
     assert options.sourceFileProvider != null;
     SourceFileEnvironment environment = createSourceFileEnvironment(proguardMapId);
     String sourceFile = options.sourceFileProvider.get(environment);
     DexString dexSourceFile =
         sourceFile == null ? null : options.itemFactory.createString(sourceFile);
-    appView.appInfo().classes().forEach(clazz -> clazz.setSourceFile(dexSourceFile));
+    classes.forEach(clazz -> clazz.setSourceFile(dexSourceFile));
     return dexSourceFile;
   }
 
diff --git a/src/main/java/com/android/tools/r8/dump/CompilerDump.java b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
index 5e9b815..1cf9373 100644
--- a/src/main/java/com/android/tools/r8/dump/CompilerDump.java
+++ b/src/main/java/com/android/tools/r8/dump/CompilerDump.java
@@ -3,13 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dump;
 
+import static com.android.tools.r8.utils.AndroidApp.dumpR8ExcludeFileName;
+import static com.android.tools.r8.utils.AndroidApp.dumpR8IncludeFileName;
+
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import java.io.BufferedReader;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.function.Consumer;
 
 public class CompilerDump {
@@ -82,6 +85,14 @@
     return directory.resolve("desugared-library.json");
   }
 
+  public Path getR8PartialIncludeFile() {
+    return directory.resolve(dumpR8IncludeFileName);
+  }
+
+  public Path getR8PartialExcludeFile() {
+    return directory.resolve(dumpR8ExcludeFileName);
+  }
+
   public void sanitizeProguardConfig(ProguardConfigSanitizer sanitizer) throws IOException {
     try (BufferedReader reader = Files.newBufferedReader(getProguardConfigFile())) {
       String next = reader.readLine();
@@ -95,10 +106,28 @@
   public DumpOptions getBuildProperties() throws IOException {
     if (Files.exists(getBuildPropertiesFile())) {
       DumpOptions.Builder builder = new DumpOptions.Builder();
-      DumpOptions.parse(
-          FileUtils.readTextFile(getBuildPropertiesFile(), StandardCharsets.UTF_8), builder);
+      DumpOptions.parse(FileUtils.readTextFile(getBuildPropertiesFile()), builder);
       return builder.build();
     }
     return null;
   }
+
+  public List<String> getR8PartialIncludePatterns() throws IOException {
+    if (Files.exists(getR8PartialIncludeFile())) {
+      List<String> includePatterns = FileUtils.readAllLines(getR8PartialIncludeFile());
+      assert includePatterns.stream().noneMatch(String::isEmpty);
+      return includePatterns;
+    }
+    return null;
+  }
+
+  public List<String> getR8PartialExcludePatternsOrDefault(List<String> defaultValue)
+      throws IOException {
+    if (Files.exists(getR8PartialExcludeFile())) {
+      List<String> excludePatterns = FileUtils.readAllLines(getR8PartialExcludeFile());
+      assert excludePatterns.stream().noneMatch(String::isEmpty);
+      return excludePatterns;
+    }
+    return defaultValue;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 052a6de..31835aa 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.partial.R8PartialCompilationConfiguration;
 import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
@@ -82,6 +83,7 @@
   private final boolean enableMissingLibraryApiModeling;
   private final boolean isAndroidPlatformBuild;
   private final AndroidResourceProvider androidResourceProvider;
+  private final R8PartialCompilationConfiguration partialCompilationConfiguration;
   private final Map<String, String> systemProperties;
 
   // TraceReferences only.
@@ -116,6 +118,7 @@
       boolean dumpInputToFile,
       String traceReferencesConsumer,
       AndroidResourceProvider androidResourceProvider,
+      R8PartialCompilationConfiguration partialCompilationConfiguration,
       Optional<Boolean> optimizedResourceShrinking) {
     this.backend = backend;
     this.tool = tool;
@@ -143,6 +146,7 @@
     this.dumpInputToFile = dumpInputToFile;
     this.traceReferencesConsumer = traceReferencesConsumer;
     this.androidResourceProvider = androidResourceProvider;
+    this.partialCompilationConfiguration = partialCompilationConfiguration;
   }
 
   public String getBuildPropertiesFileContent() {
@@ -378,6 +382,14 @@
     return androidResourceProvider;
   }
 
+  public boolean hasPartialCompilationConfiguration() {
+    return partialCompilationConfiguration != null && partialCompilationConfiguration.isEnabled();
+  }
+
+  public R8PartialCompilationConfiguration getPartialCompilationConfiguration() {
+    return partialCompilationConfiguration;
+  }
+
   public static class Builder {
     // Initialize backend to DEX for backwards compatibility.
     private Backend backend = Backend.DEX;
@@ -401,6 +413,7 @@
     private Collection<ArtProfileProvider> artProfileProviders;
     private Collection<StartupProfileProvider> startupProfileProviders;
     private AndroidResourceProvider androidResourceProvider;
+    private R8PartialCompilationConfiguration partialCompilationConfiguration;
     private Optional<Boolean> optimizedResourceShrinking = Optional.empty();
 
     private boolean enableMissingLibraryApiModeling = false;
@@ -597,16 +610,19 @@
           dumpInputToFile,
           traceReferencesConsumer,
           androidResourceProvider,
+          partialCompilationConfiguration,
           optimizedResourceShrinking);
     }
 
-    public AndroidResourceProvider getAndroidResourceProvider() {
-      return androidResourceProvider;
-    }
-
     public Builder setAndroidResourceProvider(AndroidResourceProvider androidResourceProvider) {
       this.androidResourceProvider = androidResourceProvider;
       return this;
     }
+
+    public Builder setPartialCompilationConfiguration(
+        R8PartialCompilationConfiguration partialCompilationConfiguration) {
+      this.partialCompilationConfiguration = partialCompilationConfiguration;
+      return this;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 4c16fe2..939cd35 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -212,6 +212,16 @@
     return definitionForWithoutExistenceAssert(type) != null;
   }
 
+  public final boolean hasDefinitionForWithoutExistenceAssert(DexMember<?, ?> member) {
+    return member.isDefinedOnClass(definitionForWithoutExistenceAssert(member.getHolderType()));
+  }
+
+  public final boolean hasDefinitionForWithoutExistenceAssert(DexReference reference) {
+    return reference.isDexType()
+        ? hasDefinitionForWithoutExistenceAssert(reference.asDexType())
+        : hasDefinitionForWithoutExistenceAssert(reference.asDexMember());
+  }
+
   @SuppressWarnings("ReferenceEquality")
   public DexClass definitionForDesugarDependency(DexClass dependent, DexType type) {
     if (dependent.type == type) {
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 17b3c2d..be9a796 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -281,7 +281,7 @@
     return new AppView<>(
         appInfo,
         ArtProfileCollection.createInitialArtProfileCollection(appInfo, appInfo.options()),
-        StartupProfile.empty(),
+        StartupProfile.createInitialStartupProfileForD8(appInfo.app()),
         WholeProgramOptimizations.OFF,
         mapper,
         timing);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 14c4684..1e64e9e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -213,6 +213,12 @@
     this.sourceFile = sourceFile;
   }
 
+  public DexString getAndClearSourceFile() {
+    DexString sourceFile = getSourceFile();
+    setSourceFile(null);
+    return sourceFile;
+  }
+
   public Iterable<DexClassAndField> classFields() {
     return Iterables.transform(fields(), field -> DexClassAndField.create(this, field));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index d60a682..0dc6918 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1274,7 +1274,11 @@
   }
 
   public static DexEncodedMethod createDesugaringForwardingMethod(
-      DexClassAndMethod target, DexClass clazz, DexMethod forwardMethod, DexItemFactory factory) {
+      DexClassAndMethod target,
+      DexClass clazz,
+      DexMethod forwardMethod,
+      DexItemFactory factory,
+      boolean targetIsStatic) {
     assert forwardMethod != null;
     // New method will have the same name, proto, and also all the flags of the
     // default method, including bridge flag.
@@ -1285,16 +1289,23 @@
     newFlags.unsetAbstract();
     // Holder is companion class, or retarget method, not an interface.
     boolean isInterfaceMethodReference = false;
+    ForwardMethodBuilder builder =
+        ForwardMethodBuilder.builder(factory).setNonStaticSource(newMethod);
+    if (targetIsStatic) {
+      builder.setStaticTarget(forwardMethod, isInterfaceMethodReference);
+    } else {
+      builder.setVirtualTarget(forwardMethod, isInterfaceMethodReference);
+    }
+    if (forwardMethod.getReturnType().isNotIdenticalTo(target.getReturnType())) {
+      assert target.getReturnType().isVoidType();
+      builder.setIgnoreTargetResult();
+    }
     return syntheticBuilder()
         .setMethod(newMethod)
         .setAccessFlags(newFlags)
         .setGenericSignature(MethodTypeSignature.noSignature())
         .setAnnotations(DexAnnotationSet.empty())
-        .setCode(
-            ForwardMethodBuilder.builder(factory)
-                .setNonStaticSource(newMethod)
-                .setStaticTarget(forwardMethod, isInterfaceMethodReference)
-                .buildCf())
+        .setCode(builder.buildCf())
         .setApiLevelForDefinition(target.getDefinition().getApiLevelForDefinition())
         .setApiLevelForCode(target.getDefinition().getApiLevelForCode())
         .build();
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 d02a4cd..27f31fa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -73,6 +73,7 @@
   public static final String throwableDescriptorString = "Ljava/lang/Throwable;";
   public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
   public static final String recordTagDescriptorString = "Lcom/android/tools/r8/RecordTag;";
+  public static final String autoCloseableTagString = "Lcom/android/tools/r8/AutoCloseableTag;";
   public static final String recordDescriptorString = "Ljava/lang/Record;";
   public static final String desugarVarHandleDescriptorString =
       "Lcom/android/tools/r8/DesugarVarHandle;";
@@ -260,6 +261,7 @@
   public final DexString objectDescriptor = createString("Ljava/lang/Object;");
   public final DexString recordDescriptor = createString(recordDescriptorString);
   public final DexString recordTagDescriptor = createString(recordTagDescriptorString);
+  public final DexString autoCloseableTagDescriptor = createString(autoCloseableTagString);
   public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
   public final DexString classDescriptor = createString("Ljava/lang/Class;");
   public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
@@ -441,6 +443,7 @@
   public final DexType objectType = createStaticallyKnownType(objectDescriptor);
   public final DexType recordType = createStaticallyKnownType(recordDescriptor);
   public final DexType recordTagType = createStaticallyKnownType(recordTagDescriptor);
+  public final DexType autoCloseableTagType = createStaticallyKnownType(autoCloseableTagDescriptor);
   public final DexType objectArrayType = createStaticallyKnownType(objectArrayDescriptor);
   public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor);
   public final DexType enumType = createStaticallyKnownType(enumDescriptor);
@@ -665,6 +668,7 @@
       createStaticallyKnownType(androidContentContentProviderClientDescriptorString);
   public final DexType androidDrmDrmManagerClientType =
       createStaticallyKnownType(androidDrmDrmManagerClientDescriptorString);
+  public final DexType androidMediaMediaDrm = createStaticallyKnownType("Landroid/media/MediaDrm;");
   public final DexType androidMediaMediaDrmType =
       createStaticallyKnownType(androidMediaMediaDrmDescriptorString);
   public final DexType androidMediaMediaMetadataRetrieverType =
@@ -894,6 +898,11 @@
       createStaticallyKnownType(desugarMethodHandlesLookupDescriptorString);
   public final DexType mockitoType = createStaticallyKnownType("Lorg/mockito/Mockito;");
 
+  public final DexType javaUtilConcurrentExecutorServiceType =
+      createStaticallyKnownType("Ljava/util/concurrent/ExecutorService;");
+  public final DexType javaUtilConcurrentForkJoinPoolType =
+      createStaticallyKnownType("Ljava/util/concurrent/ForkJoinPool;");
+
   public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
   public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
   public final IteratorMethods iteratorMethods = new IteratorMethods();
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index cae4a0e..620027f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -273,13 +273,23 @@
   }
 
   private String decode() throws UTFDataFormatException {
+    return decodeFromMutf8(content, javaLangStringLength);
+  }
+
+  public int decodePrefix(char[] out) throws UTFDataFormatException {
+    return decodePrefixFromMutf8(content, out);
+  }
+
+  public static String decodeFromMutf8(byte[] content, int javaLangStringLength)
+      throws UTFDataFormatException {
     char[] out = new char[javaLangStringLength];
-    int decodedLength = decodePrefix(out);
+    int decodedLength = decodePrefixFromMutf8(content, out);
     return new String(out, 0, decodedLength);
   }
 
   // Inspired from /dex/src/main/java/com/android/dex/Mutf8.java
-  public int decodePrefix(char[] out) throws UTFDataFormatException {
+  public static int decodePrefixFromMutf8(byte[] content, char[] out)
+      throws UTFDataFormatException {
     int s = 0;
     int p = 0;
     int prefixLength = out.length;
@@ -354,11 +364,11 @@
     return h;
   }
 
-  private UTFDataFormatException badByteException(int a) {
+  private static UTFDataFormatException badByteException(int a) {
     return new UTFDataFormatException("bad byte: " + Integer.toHexString((char) (a & 0xff)) + ")");
   }
 
-  private UTFDataFormatException badSecondByteException(int a, int b) {
+  private static UTFDataFormatException badSecondByteException(int a, int b) {
     return new UTFDataFormatException(
         "bad second byte (first: "
             + Integer.toHexString((char) (a & 0xff))
@@ -367,7 +377,7 @@
             + ")");
   }
 
-  private UTFDataFormatException badSecondOrThirdByteException(int a, int b, int c) {
+  private static UTFDataFormatException badSecondOrThirdByteException(int a, int b, int c) {
     return new UTFDataFormatException(
         "bad second or third byte (first: "
             + Integer.toHexString((char) (a & 0xff))
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index a515a46..389f92d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -34,7 +34,7 @@
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.jar.CfApplicationClassWriter;
 import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
 import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
 import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
@@ -129,12 +129,12 @@
         parsingOptions);
 
     // Read marker.
-    if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
-        && reader.getItem(CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
+    if (reader.getItemCount() > CfApplicationClassWriter.MARKER_STRING_CONSTANT_POOL_INDEX
+        && reader.getItem(CfApplicationClassWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
       try {
         Object maybeMarker =
             reader.readConst(
-                CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
+                CfApplicationClassWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
                 new char[reader.getMaxStringLength()]);
         if (maybeMarker instanceof String) {
           application.getFactory().createMarkerString((String) maybeMarker);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
index 62462bd..ae47e34 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
@@ -21,14 +23,27 @@
 
 public class InterfaceCollection {
 
-  @SuppressWarnings("ReferenceEquality")
   public static boolean isKnownToImplement(
       DexType iface, DexType implementor, InternalOptions options) {
     if (options.canHaveZipFileWithMissingCloseableBug()
-        && implementor == options.dexItemFactory().zipFileType
-        && iface == options.dexItemFactory().closeableType) {
+        && implementor.isIdenticalTo(options.dexItemFactory().zipFileType)
+        && iface.isIdenticalTo(options.dexItemFactory().closeableType)) {
       return false;
     }
+    if (options.canHaveMissingImplementsAutoCloseableInterface()
+        && iface.isIdenticalTo(options.dexItemFactory().autoCloseableType)) {
+      BooleanBox booleanBox = new BooleanBox(true);
+      AutoCloseableRetargeterHelper.forEachAutoCloseableMissingSubimplementation(
+          type -> {
+            if (type.isIdenticalTo(implementor)) {
+              booleanBox.set(false);
+            }
+          },
+          options.getMinApiLevel(),
+          options.dexItemFactory(),
+          true);
+      return booleanBox.get();
+    }
     return true;
   }
 
@@ -44,10 +59,6 @@
               || isKnownToImplement(iface, implementor.getType(), options));
     }
 
-    public Builder addInterface(DexType iface, DexType implementor, InternalOptions options) {
-      return addInterface(iface, isKnownToImplement(iface, implementor, options));
-    }
-
     public Builder addInterface(DexType type, boolean isKnown) {
       interfaces.compute(
           type,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
index 855b473..52386c8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
@@ -311,6 +311,13 @@
               .isPossiblyFalse()) {
         return false;
       }
+      if (appView.options().canHaveMissingImplementsAutoCloseableInterface()
+          && instanceOfBaseType.isIdenticalTo(appView.dexItemFactory().autoCloseableType)
+          && instanceOfClass.isLibraryClass()) {
+        // Library classes may be messed up since they may implement AutoCloseable from a different
+        // api level than the api level they are introduced.
+        return false;
+      }
     }
 
     Value inValue = instanceOf.value();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index ee7aa09..04fb94e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -926,21 +926,24 @@
 
       initializeMathExactApis(factory, factory.mathType);
 
-      // android.content.res.ContentProviderClient
+      // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+      if (!appView.options().shouldDesugarAutoCloseable()) {
+        // android.content.res.ContentProviderClient
 
-      // void android.content.ContentProviderClient.close()
-      addProvider(
-          new InvokeRewriter(
-              factory.androidContentContentProviderClientMembers.close,
-              ContentProviderClientMethodRewrites.rewriteClose()));
+        // void android.content.ContentProviderClient.close()
+        addProvider(
+            new InvokeRewriter(
+                factory.androidContentContentProviderClientMembers.close,
+                ContentProviderClientMethodRewrites.rewriteClose()));
 
-      // android.drm.DrmManagerClient
+        // android.drm.DrmManagerClient
 
-      // void android.drm.DrmManagerClient.close()
-      addProvider(
-          new InvokeRewriter(
-              factory.androidDrmDrmManagerClientMembers.close,
-              DrmManagerClientMethodRewrites.rewriteClose()));
+        // void android.drm.DrmManagerClient.close()
+        addProvider(
+            new InvokeRewriter(
+                factory.androidDrmDrmManagerClientMembers.close,
+                DrmManagerClientMethodRewrites.rewriteClose()));
+      }
     }
 
     /**
@@ -1197,11 +1200,14 @@
               "stripTrailingZeros",
               bigDecimal));
 
-      // void android.drm.DrmManagerClient.close()
-      addProvider(
-          new InvokeRewriter(
-              factory.androidMediaMetadataRetrieverMembers.close,
-              MediaMetadataRetrieverMethodRewrites.rewriteClose()));
+      // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+      if (!appView.options().shouldDesugarAutoCloseable()) {
+        // void android.media.MetadataRetriever.close()
+        addProvider(
+            new InvokeRewriter(
+                factory.androidMediaMetadataRetrieverMembers.close,
+                MediaMetadataRetrieverMethodRewrites.rewriteClose()));
+      }
     }
 
     private void initializeAndroidRObjectsMethodProviderWithSupplier(DexItemFactory factory) {
@@ -1448,13 +1454,16 @@
           new InvokeRewriter(
               factory.androidUtilSparseArrayMembers.set, SparseArrayMethodRewrites.rewriteSet()));
 
-      // android.content.res.TypedArray
+      // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+      if (!appView.options().shouldDesugarAutoCloseable()) {
+        // android.content.res.TypedArray
 
-      // void android.content.res.TypedArray.close()
-      addProvider(
-          new InvokeRewriter(
-              factory.androidContentResTypedArrayMembers.close,
-              TypedArrayMethodRewrites.rewriteClose()));
+        // void android.content.res.TypedArray.close()
+        addProvider(
+            new InvokeRewriter(
+                factory.androidContentResTypedArrayMembers.close,
+                TypedArrayMethodRewrites.rewriteClose()));
+      }
     }
 
     private void initializeAndroidSv2MethodProviders(DexItemFactory factory) {
@@ -1874,17 +1883,20 @@
                     (Integer) versionCodeFull[1])));
       }
 
-      // void java.util.concurrent.ExecutorService.close()
-      type = factory.createType("Ljava/util/concurrent/ExecutorService;");
-      name = factory.createString("close");
-      proto = factory.createProto(factory.voidType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(
-          new StatifyingMethodGenerator(
-              method,
-              BackportedMethods::ExecutorServiceMethods_closeExecutorService,
-              "closeExecutorService",
-              type));
+      // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+      if (!appView.options().shouldDesugarAutoCloseable()) {
+        // void java.util.concurrent.ExecutorService.close()
+        type = factory.createType("Ljava/util/concurrent/ExecutorService;");
+        name = factory.createString("close");
+        proto = factory.createProto(factory.voidType);
+        method = factory.createMethod(type, proto, name);
+        addProvider(
+            new StatifyingMethodGenerator(
+                method,
+                BackportedMethods::ExecutorServiceMethods_closeExecutorService,
+                "closeExecutorService",
+                type));
+      }
     }
 
     private void initializeAndroidUMethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index ace4c1e..2363fe4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibRewriterEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
@@ -77,7 +78,8 @@
         ApiInvokeOutlinerDesugaringEventConsumer,
         VarHandleDesugaringEventConsumer,
         DesugaredLibRewriterEventConsumer,
-        TypeSwitchDesugaringEventConsumer {
+        TypeSwitchDesugaringEventConsumer,
+        AutoCloseableRetargeterEventConsumer {
 
   public static CfInstructionDesugaringEventConsumer createForD8(
       AppView<?> appView,
@@ -259,6 +261,11 @@
     }
 
     @Override
+    public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
+    @Override
     public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
       clazz
           .programMethods()
@@ -558,6 +565,11 @@
     }
 
     @Override
+    public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+      // Intentionally empty. The method will be hit by tracing if required.
+    }
+
+    @Override
     public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
       // Intentionally empty. The class will be hit by tracing if required.
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index f31a4a7..dd8e727 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPICallbackSynthesizer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.DesugaredLibraryDisableDesugarerPostProcessor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterPostProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterPostProcessor;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.records.RecordClassDesugaring;
@@ -64,6 +65,9 @@
           && !appView.options().getLibraryDesugaringOptions().isDesugaredLibraryCompilation()) {
         desugarings.add(new DesugaredLibraryRetargeterPostProcessor(appView));
       }
+      if (appView.options().testing.enableAutoCloseableDesugaring) {
+        desugarings.add(new AutoCloseableRetargeterPostProcessor(appView));
+      }
       if (interfaceMethodProcessorFacade != null) {
         desugarings.add(interfaceMethodProcessorFacade);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index 9cdb6e6..e1a3cd9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -11,10 +11,12 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer.AutoCloseableRetargeterPostProcessingEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessingDesugaringEventConsumer;
@@ -35,7 +37,8 @@
 public abstract class CfPostProcessingDesugaringEventConsumer
     implements DesugaredLibraryRetargeterPostProcessingEventConsumer,
         InterfaceProcessingDesugaringEventConsumer,
-        DesugaredLibraryAPICallbackSynthesizorEventConsumer {
+        DesugaredLibraryAPICallbackSynthesizorEventConsumer,
+        AutoCloseableRetargeterPostProcessingEventConsumer {
 
   public static CfPostProcessingDesugaringEventConsumer createForD8(
       AppView<?> appView,
@@ -108,6 +111,12 @@
     }
 
     @Override
+    public void acceptAutoCloseableInterfaceInjection(
+        DexProgramClass clazz, DexClass newInterface) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptEmulatedInterfaceMarkerInterface(
         DexProgramClass clazz, DexClasspathClass newInterface) {
       // Intentionally empty.
@@ -169,6 +178,17 @@
     public void acceptGenericApiConversionStub(DexClasspathClass dexClasspathClass) {
       // Intentionally empty.
     }
+
+    @Override
+    public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+      addMethodToReprocess(method);
+    }
+
+    @Override
+    public void acceptAutoCloseableForwardingMethod(
+        ProgramMethod method, ProgramDefinition context) {
+      addMethodToReprocess(method);
+    }
   }
 
   public static class R8PostProcessingDesugaringEventConsumer
@@ -218,6 +238,12 @@
     }
 
     @Override
+    public void acceptAutoCloseableInterfaceInjection(
+        DexProgramClass clazz, DexClass newInterface) {
+      additions.injectInterface(clazz, newInterface);
+    }
+
+    @Override
     public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
       additions.addLiveClasspathClass(clazz);
     }
@@ -271,5 +297,16 @@
     public void acceptGenericApiConversionStub(DexClasspathClass clazz) {
       additions.addLiveClasspathClass(clazz);
     }
+
+    @Override
+    public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+      additions.addLiveMethod(method);
+    }
+
+    @Override
+    public void acceptAutoCloseableForwardingMethod(
+        ProgramMethod method, ProgramDefinition context) {
+      additions.addLiveMethod(method);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 4442dfe..604920f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.DesugaredLibraryDisableDesugarer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryLibRewriter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.icce.AlwaysThrowingInstructionDesugaring;
@@ -120,6 +121,9 @@
     if (desugaredLibraryRetargeter != null) {
       desugarings.add(desugaredLibraryRetargeter);
     }
+    if (appView.options().shouldDesugarAutoCloseable()) {
+      desugarings.add(new AutoCloseableRetargeter(appView));
+    }
     disableDesugarer = DesugaredLibraryDisableDesugarer.create(appView);
     if (disableDesugarer != null) {
       desugarings.add(disableDesugarer);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 24224c7..02eda10 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -106,6 +106,7 @@
     factory.createSynthesizedType("Ljava/util/OptionalLong;");
     factory.createSynthesizedType("Ljava/util/Set;");
     factory.createSynthesizedType("Ljava/util/concurrent/ExecutorService;");
+    factory.createSynthesizedType("Ljava/util/concurrent/ForkJoinPool;");
     factory.createSynthesizedType("Ljava/util/concurrent/TimeUnit;");
     factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReference;");
     factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReferenceArray;");
@@ -2430,12 +2431,42 @@
     CfLabel label13 = new CfLabel();
     CfLabel label14 = new CfLabel();
     CfLabel label15 = new CfLabel();
+    CfLabel label16 = new CfLabel();
+    CfLabel label17 = new CfLabel();
+    CfLabel label18 = new CfLabel();
     return new CfCode(
         method.holder,
         4,
         4,
         ImmutableList.of(
             label0,
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Landroid/os/Build$VERSION;"),
+                    factory.intType,
+                    factory.createString("SDK_INT"))),
+            new CfConstNumber(23, ValueType.INT),
+            new CfIfCmp(IfType.LE, ValueType.INT, label3),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/util/concurrent/ForkJoinPool;"),
+                    factory.createProto(factory.createType("Ljava/util/concurrent/ForkJoinPool;")),
+                    factory.createString("commonPool")),
+                false),
+            new CfIfCmp(IfType.NE, ValueType.OBJECT, label3),
+            label2,
+            new CfReturnVoid(),
+            label3,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/util/concurrent/ExecutorService;"))
+                    })),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 185,
@@ -2445,10 +2476,10 @@
                     factory.createString("isTerminated")),
                 true),
             new CfStore(ValueType.INT, 1),
-            label1,
+            label4,
             new CfLoad(ValueType.INT, 1),
-            new CfIf(IfType.NE, ValueType.INT, label14),
-            label2,
+            new CfIf(IfType.NE, ValueType.INT, label17),
+            label5,
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 185,
@@ -2457,10 +2488,10 @@
                     factory.createProto(factory.voidType),
                     factory.createString("shutdown")),
                 true),
-            label3,
+            label6,
             new CfConstNumber(0, ValueType.INT),
             new CfStore(ValueType.INT, 2),
-            label4,
+            label7,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
@@ -2471,8 +2502,8 @@
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 1),
-            new CfIf(IfType.NE, ValueType.INT, label12),
-            label5,
+            new CfIf(IfType.NE, ValueType.INT, label15),
+            label8,
             new CfLoad(ValueType.OBJECT, 0),
             new CfConstNumber(1, ValueType.LONG),
             new CfStaticFieldRead(
@@ -2491,9 +2522,9 @@
                     factory.createString("awaitTermination")),
                 true),
             new CfStore(ValueType.INT, 1),
-            label6,
-            new CfGoto(label4),
-            label7,
+            label9,
+            new CfGoto(label7),
+            label10,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
@@ -2508,10 +2539,10 @@
                         FrameType.initializedNonNullReference(
                             factory.createType("Ljava/lang/InterruptedException;"))))),
             new CfStore(ValueType.OBJECT, 3),
-            label8,
+            label11,
             new CfLoad(ValueType.INT, 2),
-            new CfIf(IfType.NE, ValueType.INT, label11),
-            label9,
+            new CfIf(IfType.NE, ValueType.INT, label14),
+            label12,
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 185,
@@ -2521,10 +2552,10 @@
                     factory.createString("shutdownNow")),
                 true),
             new CfStackInstruction(CfStackInstruction.Opcode.Pop),
-            label10,
+            label13,
             new CfConstNumber(1, ValueType.INT),
             new CfStore(ValueType.INT, 2),
-            label11,
+            label14,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
@@ -2534,8 +2565,8 @@
                       FrameType.intType(),
                       FrameType.intType()
                     })),
-            new CfGoto(label4),
-            label12,
+            new CfGoto(label7),
+            label15,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2},
@@ -2546,8 +2577,8 @@
                       FrameType.intType()
                     })),
             new CfLoad(ValueType.INT, 2),
-            new CfIf(IfType.EQ, ValueType.INT, label14),
-            label13,
+            new CfIf(IfType.EQ, ValueType.INT, label17),
+            label16,
             new CfInvoke(
                 184,
                 factory.createMethod(
@@ -2562,7 +2593,7 @@
                     factory.createProto(factory.voidType),
                     factory.createString("interrupt")),
                 false),
-            label14,
+            label17,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1},
@@ -2572,13 +2603,13 @@
                       FrameType.intType()
                     })),
             new CfReturnVoid(),
-            label15),
+            label18),
         ImmutableList.of(
             new CfTryCatch(
-                label5,
-                label6,
+                label8,
+                label9,
                 ImmutableList.of(factory.createType("Ljava/lang/InterruptedException;")),
-                ImmutableList.of(label7))),
+                ImmutableList.of(label10))),
         ImmutableList.of());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/PrefixRewritingNamingLens.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/PrefixRewritingNamingLens.java
index 77e22b9..91fd4a5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/PrefixRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/PrefixRewritingNamingLens.java
@@ -116,4 +116,9 @@
   public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
     return namingLens.verifyRenamingConsistentWithResolution(item);
   }
+
+  @Override
+  public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+    return namingLens;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java
new file mode 100644
index 0000000..703067b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java
@@ -0,0 +1,186 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper.createCloseMethod;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper.lookupSuperIncludingInterfaces;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.IntConsumer;
+import org.objectweb.asm.Opcodes;
+
+public class AutoCloseableRetargeter implements CfInstructionDesugaring {
+
+  private final AppView<?> appView;
+  private final AutoCloseableRetargeterHelper data;
+
+  public AutoCloseableRetargeter(AppView<?> appView) {
+    this.appView = appView;
+    this.data =
+        new AutoCloseableRetargeterHelper(
+            appView.options().getMinApiLevel(), appView.dexItemFactory());
+  }
+
+  public DexMethod synthesizeDispatcher(
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      AutoCloseableRetargeterEventConsumer eventConsumer) {
+    DexItemFactory factory = appView.dexItemFactory();
+    LinkedHashMap<DexType, DexMethod> dispatchCases =
+        data.synthesizeDispatchCases(appView, context, eventConsumer, methodProcessingContext);
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.AUTOCLOSEABLE_DISPATCHER,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                methodBuilder ->
+                    methodBuilder
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setProto(factory.createProto(factory.voidType, factory.objectType))
+                        .setCode(
+                            methodSig ->
+                                new EmulateDispatchSyntheticCfCodeProvider(
+                                        methodSig.getHolderType(),
+                                        data.createThrowUnsupportedException(
+                                                appView,
+                                                context,
+                                                eventConsumer,
+                                                methodProcessingContext::createUniqueContext)
+                                            .getReference(),
+                                        createCloseMethod(factory, factory.autoCloseableType),
+                                        dispatchCases,
+                                        EmulateDispatchType.AUTO_CLOSEABLE,
+                                        appView)
+                                    .generateCfCode()));
+    eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+    return method.getReference();
+  }
+
+  @Override
+  public void acceptRelevantAsmOpcodes(IntConsumer consumer) {
+    consumer.accept(Opcodes.INVOKEVIRTUAL);
+    consumer.accept(Opcodes.INVOKESPECIAL);
+    consumer.accept(Opcodes.INVOKEINTERFACE);
+  }
+
+  @Override
+  public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
+    if (instruction.isInvoke() && !instruction.isInvokeStatic()) {
+      return computeInvokeDescription(instruction, context);
+    }
+    return DesugarDescription.nothing();
+  }
+
+  private DesugarDescription computeInvokeDescription(
+      CfInstruction instruction, ProgramMethod context) {
+    if (appView
+        .getSyntheticItems()
+        .isSyntheticOfKind(context.getHolderType(), kinds -> kinds.AUTOCLOSEABLE_DISPATCHER)) {
+      return DesugarDescription.nothing();
+    }
+    CfInvoke cfInvoke = instruction.asInvoke();
+    DexMethod invokedMethod = cfInvoke.getMethod();
+    if (!data.hasCloseMethodName(invokedMethod) || invokedMethod.getArity() > 0) {
+      return DesugarDescription.nothing();
+    }
+    AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodLegacy(invokedMethod, cfInvoke.isInterface());
+    if (!resolutionResult.isSingleResolution()) {
+      return DesugarDescription.nothing();
+    }
+    assert resolutionResult.getSingleTarget() != null;
+    DexMethod reference = resolutionResult.getSingleTarget().getReference();
+    if (data.shouldEmulateMethod(reference)) {
+      return computeNewTarget(reference, cfInvoke.isInvokeSuper(context.getHolderType()), context);
+    }
+    return DesugarDescription.nothing();
+  }
+
+  private DesugarDescription computeNewTarget(
+      DexMethod singleTarget, boolean superInvoke, ProgramMethod context) {
+    if (superInvoke) {
+      DexClassAndMethod superMethod =
+          lookupSuperIncludingInterfaces(appView, singleTarget, context.getContextClass());
+      if (superMethod != null && superMethod.isLibraryMethod()) {
+        DexType holderType = superMethod.getHolderType();
+        if (data.superTargetsToRewrite().contains(holderType)) {
+          return createWithTarget(
+              singleTarget,
+              (eventConsumer, methodProcessingContext) ->
+                  data.synthesizeDispatchCase(
+                      appView,
+                      holderType,
+                      context,
+                      eventConsumer,
+                      methodProcessingContext::createUniqueContext));
+        }
+      }
+      return DesugarDescription.nothing();
+    }
+    return createWithTarget(
+        singleTarget,
+        (eventConsumer, methodProcessingContext) ->
+            synthesizeDispatcher(context, methodProcessingContext, eventConsumer));
+  }
+
+  private DesugarDescription createWithTarget(
+      DexMethod target,
+      BiFunction<CfInstructionDesugaringEventConsumer, MethodProcessingContext, DexMethod>
+          methodProvider) {
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (position,
+                freshLocalProvider,
+                localStackAllocator,
+                desugaringInfo,
+                eventConsumer,
+                context,
+                methodProcessingContext,
+                desugarings,
+                dexItemFactory) -> {
+              DexMethod newInvokeTarget =
+                  methodProvider.apply(eventConsumer, methodProcessingContext);
+              assert appView.definitionFor(newInvokeTarget.getHolderType()) != null;
+              assert !appView.definitionFor(newInvokeTarget.getHolderType()).isInterface();
+              List<CfInstruction> instructions = new ArrayList<>();
+              if (appView.getSyntheticItems().isSynthetic(newInvokeTarget.getHolderType())) {
+                instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, false));
+              } else {
+                instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newInvokeTarget, false));
+              }
+              if (target.getReturnType().isVoidType()
+                  && !newInvokeTarget.getReturnType().isVoidType()) {
+                instructions.add(new CfStackInstruction(Opcode.Pop));
+              }
+              return instructions;
+            })
+        .build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java
new file mode 100644
index 0000000..9a29029
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface AutoCloseableRetargeterEventConsumer {
+
+  void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context);
+
+  interface AutoCloseableRetargeterPostProcessingEventConsumer
+      extends AutoCloseableRetargeterEventConsumer {
+
+    void acceptAutoCloseableForwardingMethod(ProgramMethod method, ProgramDefinition context);
+
+    void acceptAutoCloseableInterfaceInjection(DexProgramClass clazz, DexClass newInterface);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java
new file mode 100644
index 0000000..3597e57
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java
@@ -0,0 +1,234 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter;
+
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
+import com.android.tools.r8.ir.synthetic.ThrowCfCodeProvider;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableSet;
+import java.util.LinkedHashMap;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class AutoCloseableRetargeterHelper {
+
+  private final AndroidApiLevel minApiLevel;
+  private final DexItemFactory factory;
+  private final DexString close;
+  private final Set<DexMethod> methodsToEmulate;
+
+  public AutoCloseableRetargeterHelper(AndroidApiLevel minApiLevel, DexItemFactory factory) {
+    this.minApiLevel = minApiLevel;
+    this.factory = factory;
+    this.close = factory.createString("close");
+    this.methodsToEmulate = methodsToEmulate();
+  }
+
+  static DexMethod createCloseMethod(DexItemFactory factory, DexType holderType) {
+    return factory.createMethod(holderType, factory.createProto(factory.voidType), "close");
+  }
+
+  public boolean hasCloseMethodName(DexMethod method) {
+    return method.getName().isIdenticalTo(close);
+  }
+
+  public boolean shouldEmulateMethod(DexMethod method) {
+    return methodsToEmulate.contains(method);
+  }
+
+  // This includes all library types which implements AutoCloseable#close() and their subtypes.
+  // We exclude android.media.MediaDrm which is final and rewritten by the backportedMethodRewriter.
+  private Set<DexMethod> methodsToEmulate() {
+    ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+    forEachAutoCloseableMissingSubimplementation(
+        type -> {
+          if (!type.isIdenticalTo(factory.androidMediaMediaDrmType)) {
+            builder.add(createCloseMethod(factory, type));
+          }
+        });
+    builder.add(createCloseMethod(factory, factory.autoCloseableType));
+    return builder.build();
+  }
+
+  // This includes all library types which implements directly AutoCloseable#close() including
+  // android.media.MediaDrm.
+  public static void forEachAutoCloseableMissingSubimplementation(
+      Consumer<DexType> consumer,
+      AndroidApiLevel minApiLevel,
+      DexItemFactory factory,
+      boolean withSubtypes) {
+    if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.V)) {
+      consumer.accept(factory.javaUtilConcurrentExecutorServiceType);
+      consumer.accept(factory.javaUtilConcurrentForkJoinPoolType);
+      if (withSubtypes) {
+        consumer.accept(factory.createType("Ljava/util/concurrent/ScheduledExecutorService;"));
+        consumer.accept(factory.createType("Ljava/util/concurrent/AbstractExecutorService;"));
+        consumer.accept(factory.createType("Ljava/util/concurrent/ThreadPoolExecutor;"));
+        consumer.accept(factory.createType("Ljava/util/concurrent/ScheduledThreadPoolExecutor;"));
+      }
+    }
+    if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.R)) {
+      consumer.accept(factory.androidContentResTypedArrayType);
+    }
+    if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.P)) {
+      consumer.accept(factory.androidMediaMediaMetadataRetrieverType);
+    }
+    if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.O_MR1)) {
+      consumer.accept(factory.androidMediaMediaDrmType);
+    }
+    if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.M)) {
+      consumer.accept(factory.androidDrmDrmManagerClientType);
+      consumer.accept(factory.androidContentContentProviderClientType);
+    }
+  }
+
+  private void forEachAutoCloseableMissingSubimplementation(Consumer<DexType> consumer) {
+    forEachAutoCloseableMissingSubimplementation(consumer, minApiLevel, factory, false);
+  }
+
+  // This includes all library types which implements directly AutoCloseable#close() including
+  // android.media.MediaDrm, however, android.media.MediaDrm is final and rewritten if called
+  // directly by the backported method rewriter.
+  public LinkedHashMap<DexType, DexMethod> synthesizeDispatchCases(
+      AppView<?> appView,
+      ProgramMethod context,
+      AutoCloseableRetargeterEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    LinkedHashMap<DexType, DexMethod> map = new LinkedHashMap<>();
+    forEachAutoCloseableMissingSubimplementation(
+        type -> {
+          // ForkJoinPool has an optimized version of ExecutorService.close. ForkJoinPool is not
+          // present in 19 (added in 21) so R8 cannot use instanceof ForkJoinPool in the emulated
+          // dispatch. We rely on ForkJoinPool implementing ExecutorService and use that path.
+          if (type.isNotIdenticalTo(factory.javaUtilConcurrentForkJoinPoolType)) {
+            map.put(
+                type,
+                synthesizeDispatchCase(
+                    appView,
+                    type,
+                    context,
+                    eventConsumer,
+                    methodProcessingContext::createUniqueContext));
+          }
+        });
+    return map;
+  }
+
+  public Set<DexType> superTargetsToRewrite() {
+    ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+    forEachAutoCloseableMissingSubimplementation(builder::add);
+    return builder.build();
+  }
+
+  public DexMethod synthesizeDispatchCase(
+      AppView<?> appView,
+      DexType type,
+      ProgramDefinition context,
+      AutoCloseableRetargeterEventConsumer eventConsumer,
+      Supplier<UniqueContext> contextSupplier) {
+    assert superTargetsToRewrite().contains(type);
+    if (type.isIdenticalTo(factory.javaUtilConcurrentExecutorServiceType)
+        || type.isIdenticalTo(factory.javaUtilConcurrentForkJoinPoolType)) {
+      // For ForkJoinPool.close R8 uses the less efficient ExecutorService.close.
+      // ExecutorService.close does not however use unreachable apis and ExecutorService is present
+      // at Android api 19.
+      return synthesizeExecutorServiceDispatchCase(
+          appView, context, eventConsumer, contextSupplier);
+    }
+    if (type.isIdenticalTo(factory.androidContentResTypedArrayType)) {
+      return factory.createMethod(type, factory.createProto(factory.voidType), "recycle");
+    }
+    if (type.isIdenticalTo(factory.androidContentContentProviderClientType)) {
+      return factory.createMethod(type, factory.createProto(factory.booleanType), "release");
+    }
+    assert ImmutableSet.of(
+            factory.androidMediaMediaMetadataRetrieverType,
+            factory.androidMediaMediaDrmType,
+            factory.androidDrmDrmManagerClientType)
+        .contains(type);
+    return factory.createMethod(type, factory.createProto(factory.voidType), "release");
+  }
+
+  private DexMethod synthesizeExecutorServiceDispatchCase(
+      AppView<?> appView,
+      ProgramDefinition context,
+      AutoCloseableRetargeterEventConsumer eventConsumer,
+      Supplier<UniqueContext> contextSupplier) {
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.AUTOCLOSEABLE_FORWARDER,
+                contextSupplier.get(),
+                appView,
+                methodBuilder ->
+                    methodBuilder
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setProto(
+                            factory.createProto(
+                                factory.voidType, factory.javaUtilConcurrentExecutorServiceType))
+                        .setCode(
+                            methodSig ->
+                                BackportedMethods.ExecutorServiceMethods_closeExecutorService(
+                                    factory, methodSig)));
+    eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+    return method.getReference();
+  }
+
+  ProgramMethod createThrowUnsupportedException(
+      AppView<?> appView,
+      ProgramDefinition context,
+      AutoCloseableRetargeterEventConsumer eventConsumer,
+      Supplier<UniqueContext> contextSupplier) {
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.THROW_IAE,
+                contextSupplier.get(),
+                appView,
+                methodBuilder ->
+                    methodBuilder
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setProto(factory.createProto(factory.voidType, factory.objectType))
+                        .setCode(
+                            methodSig ->
+                                new ThrowCfCodeProvider(
+                                        appView,
+                                        methodSig.getHolderType(),
+                                        factory.illegalArgumentExceptionType,
+                                        null)
+                                    .generateCfCode()));
+    eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+    return method;
+  }
+
+  static DexClassAndMethod lookupSuperIncludingInterfaces(
+      AppView<?> appView, DexMethod target, DexProgramClass context) {
+    DexClassAndMethod superMethod =
+        appView
+            .appInfoForDesugaring()
+            .lookupSuperTarget(target, context, appView, appView.appInfoForDesugaring());
+    if (superMethod != null) {
+      return superMethod;
+    }
+    return appView
+        .appInfoForDesugaring()
+        .lookupMaximallySpecificMethod(context.getContextClass(), target);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterPostProcessor.java
new file mode 100644
index 0000000..b45317b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterPostProcessor.java
@@ -0,0 +1,211 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper.lookupSuperIncludingInterfaces;
+
+import com.android.tools.r8.contexts.CompilationContext.MainThreadContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer.AutoCloseableRetargeterPostProcessingEventConsumer;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+
+// The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
+// for inserting interfaces on library boundaries and forwarding methods in the program, and to
+// synthesize the interfaces and emulated dispatch classes in the desugared library.
+public class AutoCloseableRetargeterPostProcessor implements CfPostProcessingDesugaring {
+
+  private final AppView<?> appView;
+  private final AutoCloseableRetargeterHelper data;
+
+  public AutoCloseableRetargeterPostProcessor(AppView<?> appView) {
+    this.appView = appView;
+    this.data =
+        new AutoCloseableRetargeterHelper(
+            appView.options().getMinApiLevel(), appView.dexItemFactory());
+  }
+
+  @Override
+  public void postProcessingDesugaring(
+      Collection<DexProgramClass> programClasses,
+      CfPostProcessingDesugaringEventConsumer eventConsumer,
+      ExecutorService executorService) {
+    ensureInterfacesAndForwardingMethodsSynthesized(programClasses, eventConsumer);
+  }
+
+  @SuppressWarnings("ReferenceEquality")
+  private void ensureInterfacesAndForwardingMethodsSynthesized(
+      Collection<DexProgramClass> programClasses,
+      AutoCloseableRetargeterPostProcessingEventConsumer eventConsumer) {
+    ProcessorContext processorContext = appView.createProcessorContext();
+    MainThreadContext mainThreadContext = processorContext.createMainThreadContext();
+    for (DexProgramClass clazz : programClasses) {
+      if (clazz.superType == null) {
+        assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
+        continue;
+      }
+      if (implementsAutoCloseableAtLibraryBoundary(clazz)) {
+        ensureInterfacesAndForwardingMethodsSynthesized(eventConsumer, clazz, mainThreadContext);
+      }
+    }
+  }
+
+  private boolean implementsAutoCloseableAtLibraryBoundary(DexProgramClass clazz) {
+    if (clazz.interfaces.contains(appView.dexItemFactory().autoCloseableType)) {
+      return true;
+    }
+    WorkList<DexType> workList = collectLibrarySuperTypeAndInterfaces(clazz);
+    return libraryTypesImplementsAutoCloseable(workList, clazz);
+  }
+
+  private WorkList<DexType> collectLibrarySuperTypeAndInterfaces(DexProgramClass clazz) {
+    WorkList<DexType> workList = WorkList.newIdentityWorkList();
+    DexClass superclass = appView.definitionFor(clazz.superType);
+    // Only performs computation if superclass is a library class, but not object to filter out
+    // the most common case.
+    if (superclass != null
+        && superclass.isLibraryClass()
+        && !superclass.type.isIdenticalTo(appView.dexItemFactory().objectType)) {
+      workList.addIfNotSeen(superclass.type);
+    }
+    for (DexType itf : clazz.interfaces) {
+      DexClass superItf = appView.definitionFor(itf);
+      if (superItf != null) {
+        workList.addIfNotSeen(superItf.type);
+      }
+    }
+    return workList;
+  }
+
+  private boolean libraryTypesImplementsAutoCloseable(
+      WorkList<DexType> workList, DexProgramClass clazz) {
+    while (workList.hasNext()) {
+      DexType current = workList.next();
+      if (current.isIdenticalTo(appView.dexItemFactory().objectType)) {
+        continue;
+      }
+      DexClass currentClass = appView.definitionFor(current);
+      if (currentClass == null) {
+        reportInvalidSupertype(current, clazz);
+        continue;
+      }
+      if (currentClass.interfaces.contains(appView.dexItemFactory().autoCloseableType)) {
+        return true;
+      }
+      workList.addIfNotSeen(currentClass.superType);
+      workList.addIfNotSeen(currentClass.interfaces);
+    }
+    return false;
+  }
+
+  private void reportInvalidSupertype(DexType current, DexProgramClass origin) {
+    appView
+        .options()
+        .warningInvalidLibrarySuperclassForDesugar(
+            origin.getOrigin(),
+            origin.type,
+            current,
+            "missing",
+            ImmutableSet.of(getClose(origin.type)));
+  }
+
+  private void ensureInterfacesAndForwardingMethodsSynthesized(
+      AutoCloseableRetargeterPostProcessingEventConsumer eventConsumer,
+      DexProgramClass clazz,
+      MainThreadContext mainThreadContext) {
+    // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
+    // methods.
+    // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
+    // applies up to 24.
+    if (appView.isAlreadyLibraryDesugared(clazz)) {
+      return;
+    }
+    DexMethod close = getClose(clazz.type);
+    if (clazz.lookupVirtualMethod(close) == null) {
+      DexEncodedMethod newMethod =
+          createForwardingMethod(close, clazz, eventConsumer, mainThreadContext);
+      if (newMethod == null) {
+        // We don't support desugaring on all subtypes, in which case there is not need to inject
+        // the interface.
+        return;
+      }
+      clazz.addVirtualMethod(newMethod);
+      eventConsumer.acceptAutoCloseableForwardingMethod(new ProgramMethod(clazz, newMethod), clazz);
+    }
+    DexType autoCloseableType = appView.dexItemFactory().autoCloseableType;
+    if (clazz.interfaces.contains(autoCloseableType)) {
+      return;
+    }
+    clazz.addExtraInterfaces(
+        Collections.singletonList(new ClassTypeSignature(autoCloseableType)),
+        appView.dexItemFactory());
+    eventConsumer.acceptAutoCloseableInterfaceInjection(
+        clazz, appView.definitionFor(autoCloseableType));
+  }
+
+  @SuppressWarnings("ReferenceEquality")
+  private DexEncodedMethod createForwardingMethod(
+      DexMethod target,
+      DexProgramClass clazz,
+      AutoCloseableRetargeterPostProcessingEventConsumer eventConsumer,
+      MainThreadContext mainThreadContext) {
+    // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
+    // even if this results in invalid code, these classes are never desugared.
+    // In desugared library, emulated interface methods can be overridden by retarget lib members.
+    AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
+    assert clazz.lookupVirtualMethod(target) == null;
+    DexClassAndMethod superMethod = lookupSuperIncludingInterfaces(appView, target, clazz);
+    if (superMethod == null
+        || !data.superTargetsToRewrite().contains(superMethod.getHolderType())) {
+      return null;
+    }
+    DexMethod forwardMethod =
+        data.synthesizeDispatchCase(
+            appView,
+            superMethod.getHolderType(),
+            clazz,
+            eventConsumer,
+            () -> mainThreadContext.createUniqueContext(clazz));
+    assert forwardMethod != null && forwardMethod != target;
+    DexClassAndMethod resolvedMethod =
+        appInfoForDesugaring
+            .resolveMethodLegacy(target, target.getHolderType().isInterface(appInfoForDesugaring))
+            .getResolutionPair();
+    assert resolvedMethod != null;
+    DexEncodedMethod desugaringForwardingMethod =
+        DexEncodedMethod.createDesugaringForwardingMethod(
+            resolvedMethod,
+            clazz,
+            forwardMethod,
+            appView.dexItemFactory(),
+            appView.getSyntheticItems().isSynthetic(forwardMethod.getHolderType()));
+    desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+    return desugaringForwardingMethod;
+  }
+
+  private DexMethod getClose(DexType holder) {
+    return appView
+        .dexItemFactory()
+        .createMethod(
+            holder,
+            appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
+            "close");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
index c4be060..fa05f19 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
@@ -161,7 +161,7 @@
     assert resolvedMethod != null;
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
-            resolvedMethod, clazz, forwardMethod, appView.dexItemFactory());
+            resolvedMethod, clazz, forwardMethod, appView.dexItemFactory(), true);
     desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     return desugaringForwardingMethod;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
index 5777971..3413d51 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticClassBuilder;
 import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
@@ -284,7 +285,12 @@
     DexMethod itfMethod = emulatedInterfaceDispatchMethod(itfClass, descriptor);
     assert descriptor.getDispatchCases().isEmpty();
     return new EmulateDispatchSyntheticCfCodeProvider(
-            methodSig.getHolderType(), forwardingMethod, itfMethod, new LinkedHashMap<>(), appView)
+            methodSig.getHolderType(),
+            forwardingMethod,
+            itfMethod,
+            new LinkedHashMap<>(),
+            EmulateDispatchType.ALL_STATIC,
+            appView)
         .generateCfCode();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index c2a1998..2b89cbb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -1038,7 +1038,7 @@
     // In desugared library, emulated interface methods can be overridden by retarget lib members.
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
-            target, clazz, forwardMethod, dexItemFactory);
+            target, clazz, forwardMethod, dexItemFactory, true);
     if (!target.isProgramMethod() || target.getDefinition().isLibraryMethodOverride().isTrue()) {
       desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
index 20086db..968ecad 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedInterfaceDescriptor;
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -105,6 +106,7 @@
                         companionMethod,
                         itfMethod,
                         extraDispatchCases,
+                        EmulateDispatchType.ALL_STATIC,
                         appView)
                     .generateCfCode());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ListIterationRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ListIterationRewriter.java
index 84aa7a6..e9689e0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ListIterationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ListIterationRewriter.java
@@ -121,7 +121,7 @@
   public static boolean shouldEnable(
       AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
     TestingOptions opts = appView.options().testing;
-    if (!appView.hasLiveness() || !opts.listIterationRewritingEnabled) {
+    if (!appView.hasLiveness()) {
       return false;
     }
     if (opts.listIterationRewritingRewriteCustomIterators) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
index 9caa410..2604cef 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.ir.synthetic;
 
+import static com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType.ALL_STATIC;
+import static com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType.AUTO_CLOSEABLE;
+
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfIf;
@@ -29,8 +32,14 @@
 
 public class EmulateDispatchSyntheticCfCodeProvider extends SyntheticCfCodeProvider {
 
+  public enum EmulateDispatchType {
+    ALL_STATIC,
+    AUTO_CLOSEABLE
+  }
+
   private final DexMethod forwardingMethod;
   private final DexMethod interfaceMethod;
+  private final EmulateDispatchType dispatchType;
   private final LinkedHashMap<DexType, DexMethod> extraDispatchCases;
 
   public EmulateDispatchSyntheticCfCodeProvider(
@@ -38,11 +47,13 @@
       DexMethod forwardingMethod,
       DexMethod interfaceMethod,
       LinkedHashMap<DexType, DexMethod> extraDispatchCases,
+      EmulateDispatchType dispatchType,
       AppView<?> appView) {
     super(appView, holder);
     this.forwardingMethod = forwardingMethod;
     this.interfaceMethod = interfaceMethod;
     this.extraDispatchCases = extraDispatchCases;
+    this.dispatchType = dispatchType;
   }
 
   @Override
@@ -89,8 +100,7 @@
       // Call basic block.
       instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0));
       instructions.add(new CfCheckCast(dispatch.getKey()));
-      loadExtraParameters(instructions);
-      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, dispatch.getValue(), false));
+      forwardCall(instructions, dispatch.getValue());
       addReturn(instructions);
     }
 
@@ -98,12 +108,28 @@
     instructions.add(labels[nextLabel]);
     instructions.add(frame.clone());
     instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0));
-    loadExtraParameters(instructions);
-    instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, forwardingMethod, false));
+    forwardCall(instructions, forwardingMethod);
     addReturn(instructions);
     return standardCfCodeFromInstructions(instructions);
   }
 
+  private void forwardCall(List<CfInstruction> instructions, DexMethod method) {
+    if (dispatchType == ALL_STATIC
+        || appView.getSyntheticItems().isSynthetic(method.getHolderType())) {
+      loadExtraParameters(instructions);
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+      return;
+    }
+    assert dispatchType == AUTO_CLOSEABLE;
+    instructions.add(new CfCheckCast(method.holder));
+    loadExtraParameters(instructions);
+    if (appView.definitionFor(method.getHolderType()).isInterface()) {
+      instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, method, true));
+    } else {
+      instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
+    }
+  }
+
   private void loadExtraParameters(List<CfInstruction> instructions) {
     int index = 1;
     for (DexType type : interfaceMethod.proto.parameters.values) {
@@ -113,10 +139,10 @@
 
   @SuppressWarnings("ReferenceEquality")
   private void addReturn(List<CfInstruction> instructions) {
-    if (interfaceMethod.proto.returnType == appView.dexItemFactory().voidType) {
+    if (interfaceMethod.getReturnType().isVoidType()) {
       instructions.add(new CfReturnVoid());
     } else {
-      instructions.add(new CfReturn(ValueType.fromDexType(interfaceMethod.proto.returnType)));
+      instructions.add(new CfReturn(ValueType.fromDexType(interfaceMethod.getReturnType())));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index b8c5959..77a8cd8 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -67,6 +67,7 @@
   private InvokeType invokeType = null;
   private Boolean isInterface = null;
   private boolean castResult = false;
+  private boolean ignoreTargetResult = false;
   private boolean isConstructorDelegate = false;
   private AppInfoWithClassHierarchy appInfoForCastArguments = null;
 
@@ -157,6 +158,11 @@
     return this;
   }
 
+  public ForwardMethodBuilder setIgnoreTargetResult() {
+    ignoreTargetResult = true;
+    return this;
+  }
+
   public ForwardMethodBuilder setCastArguments(AppInfoWithClassHierarchy appInfo) {
     appInfoForCastArguments = appInfo;
     return this;
@@ -380,6 +386,9 @@
         && !targetMethod.getReturnType().isVoidType()) {
       assert ValueType.fromDexType(sourceMethod.getReturnType())
           == ValueType.fromDexType(targetMethod.getReturnType());
+    } else if (ignoreTargetResult) {
+      assert sourceMethod.getReturnType().isVoidType();
+      assert !targetMethod.getReturnType().isVoidType();
     } else {
       assert sourceMethod.getReturnType() == targetMethod.getReturnType();
     }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationClassWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationClassWriter.java
new file mode 100644
index 0000000..bb33502
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationClassWriter.java
@@ -0,0 +1,601 @@
+// Copyright (c) 2025, 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.jar;
+
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.SourceFileEnvironment;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.errors.CodeSizeOverflowDiagnostic;
+import com.android.tools.r8.errors.ConstantPoolOverflowDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
+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;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeAnnotation;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.RecordComponentInfo;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.utils.AsmUtils;
+import com.android.tools.r8.utils.ComparatorUtils;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassTooLargeException;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodTooLargeException;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypePath;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.util.CheckClassAdapter;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceMethodVisitor;
+
+public class CfApplicationClassWriter {
+
+  // First item inserted into the constant pool is the marker string which generates an UTF8 to
+  // pool index #1 and a String entry to #2, referencing #1.
+  public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
+
+  private static final CfVersion MIN_VERSION_FOR_COMPILER_GENERATED_CODE = CfVersion.V1_6;
+  private static final boolean PRINT_CF = false;
+  private static final boolean RUN_VERIFIER = false;
+
+  private final AppView<?> appView;
+  private final DexProgramClass clazz;
+  private final DexItemFactory factory;
+  private final NamingLens namingLens;
+  private final InternalOptions options;
+
+  CfApplicationClassWriter(AppView<?> appView, DexProgramClass clazz) {
+    this.appView = appView;
+    this.clazz = clazz;
+    this.factory = appView.dexItemFactory();
+    // For "pass through" classes which has already been library desugared use the identity lens.
+    this.namingLens =
+        appView.isAlreadyLibraryDesugared(clazz)
+            ? appView.getNamingLens().withoutDesugaredLibraryPrefixRewritingNamingLens()
+            : appView.getNamingLens();
+    this.options = appView.options();
+  }
+
+  private boolean isTypeMissing(DexType type) {
+    return !appView.appInfo().hasDefinitionForWithoutExistenceAssert(type);
+  }
+
+  void writeClassCatchingErrors(
+      ClassFileConsumer consumer,
+      LensCodeRewriterUtils rewriter,
+      Optional<String> markerString,
+      SourceFileEnvironment sourceFileEnvironment) {
+    assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
+    try {
+      writeClass(consumer, rewriter, markerString, sourceFileEnvironment);
+    } catch (ClassTooLargeException e) {
+      throw options.reporter.fatalError(
+          new ConstantPoolOverflowDiagnostic(
+              clazz.getOrigin(),
+              Reference.classFromBinaryName(e.getClassName()),
+              e.getConstantPoolCount()));
+    } catch (MethodTooLargeException e) {
+      throw options.reporter.fatalError(
+          new CodeSizeOverflowDiagnostic(
+              clazz.getOrigin(),
+              Reference.methodFromDescriptor(
+                  Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
+                  e.getMethodName(),
+                  e.getDescriptor()),
+              e.getCodeSize()));
+    }
+  }
+
+  private void writeClass(
+      ClassFileConsumer consumer,
+      LensCodeRewriterUtils rewriter,
+      Optional<String> markerString,
+      SourceFileEnvironment sourceFileEnvironment) {
+    ClassWriter writer = new ClassWriter(0);
+    if (markerString.isPresent()) {
+      int markerStringPoolIndex = writer.newConst(markerString.get());
+      assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
+    }
+    String sourceFile;
+    if (options.sourceFileProvider == null) {
+      sourceFile = clazz.sourceFile != null ? clazz.sourceFile.toString() : null;
+    } else {
+      sourceFile = options.sourceFileProvider.get(sourceFileEnvironment);
+    }
+    String sourceDebug = getSourceDebugExtension(clazz.annotations());
+    writer.visitSource(sourceFile, sourceDebug);
+    CfVersion version = getClassFileVersion(clazz);
+    if (version.isGreaterThanOrEqualTo(CfVersion.V1_8)) {
+      // JDK8 and after ignore ACC_SUPER so unset it.
+      clazz.accessFlags.unsetSuper();
+    } else {
+      // In all other cases set the super bit as D8/R8 do not support targeting pre 1.0.2 JDKs.
+      if (!clazz.accessFlags.isInterface()) {
+        clazz.accessFlags.setSuper();
+      }
+    }
+    boolean allowInvalidCfAccessFlags =
+        clazz.getType().getDescriptor().endsWith(factory.createString("/package-info;"));
+    int access =
+        allowInvalidCfAccessFlags || options.testing.allowInvalidCfAccessFlags
+            ? clazz.accessFlags.materialize()
+            : clazz.accessFlags.getAsCfAccessFlags();
+    if (clazz.isDeprecated()) {
+      access = AsmUtils.withDeprecated(access);
+    }
+    String desc = namingLens.lookupDescriptor(clazz.type).toString();
+    String name = namingLens.lookupInternalName(clazz.type);
+    String signature = clazz.getClassSignature().toRenamedString(namingLens, this::isTypeMissing);
+    String superName = clazz.hasSuperType() ? namingLens.lookupInternalName(clazz.superType) : null;
+    String[] interfaces = new String[clazz.interfaces.values.length];
+    for (int i = 0; i < clazz.interfaces.values.length; i++) {
+      interfaces[i] = namingLens.lookupInternalName(clazz.interfaces.values[i]);
+    }
+    assert SyntheticNaming.verifyNotInternalSynthetic(name);
+    writer.visit(version.raw(), access, name, signature, superName, interfaces);
+    appView.getSyntheticItems().writeAttributeIfIntermediateSyntheticClass(writer, clazz, appView);
+    writeAnnotations(
+        writer::visitAnnotation, writer::visitTypeAnnotation, clazz.annotations().annotations);
+    ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
+
+    if (clazz.getEnclosingMethodAttribute() != null) {
+      clazz.getEnclosingMethodAttribute().write(writer, namingLens);
+    }
+
+    if (clazz.getNestHostClassAttribute() != null) {
+      clazz.getNestHostClassAttribute().write(writer, namingLens);
+    }
+
+    for (NestMemberClassAttribute entry : clazz.getNestMembersClassAttributes()) {
+      entry.write(writer, namingLens);
+      assert clazz.getNestHostClassAttribute() == null
+          : "A nest host cannot also be a nest member.";
+    }
+
+    for (PermittedSubclassAttribute entry : clazz.getPermittedSubclassAttributes()) {
+      entry.write(writer, namingLens);
+    }
+
+    if (clazz.isRecord()) {
+      // TODO(b/274888318): Strip record components if not kept.
+      for (RecordComponentInfo info : clazz.getRecordComponents()) {
+        info.write(writer, namingLens, this::isTypeMissing, this::writeAnnotation);
+      }
+    }
+
+    for (InnerClassAttribute entry : clazz.getInnerClasses()) {
+      entry.write(writer, namingLens, options);
+    }
+
+    clazz.forEachProgramStaticField(field -> writeField(field, writer));
+    clazz.forEachProgramInstanceField(field -> writeField(field, writer));
+    if (options.desugarSpecificOptions().sortMethodsOnCfOutput) {
+      List<ProgramMethod> programMethodSorted = new ArrayList<>();
+      clazz.forEachProgramMethod(programMethodSorted::add);
+      programMethodSorted.sort(this::compareMethodsThroughLens);
+      programMethodSorted.forEach(
+          method -> writeMethod(method, version, rewriter, writer, defaults));
+    } else {
+      clazz.forEachProgramMethod(
+          method -> writeMethod(method, version, rewriter, writer, defaults));
+    }
+    writer.visitEnd();
+
+    byte[] result = writer.toByteArray();
+    if (PRINT_CF) {
+      System.out.print(printCf(result));
+      System.out.flush();
+    }
+    if (RUN_VERIFIER) {
+      // Generally, this will fail with ClassNotFoundException,
+      // so don't assert that verifyCf() returns true.
+      verifyCf(result);
+    }
+    ExceptionUtils.withConsumeResourceHandler(
+        options.reporter, handler -> consumer.accept(ByteDataView.of(result), desc, handler));
+  }
+
+  private String getSourceDebugExtension(DexAnnotationSet annotations) {
+    DexValue debugExtensions =
+        getSystemAnnotationValue(annotations, factory.annotationSourceDebugExtension);
+    if (debugExtensions == null) {
+      return null;
+    }
+    return debugExtensions.asDexValueString().getValue().toString();
+  }
+
+  @SuppressWarnings("BadImport")
+  private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
+    DexValue value = getSystemAnnotationValue(annotations, factory.annotationDefault);
+    if (value == null) {
+      return ImmutableMap.of();
+    }
+    DexEncodedAnnotation annotation = value.asDexValueAnnotation().value;
+    Builder<DexString, DexValue> builder = ImmutableMap.builder();
+    for (DexAnnotationElement element : annotation.elements) {
+      builder.put(element.name, element.value);
+    }
+    return builder.build();
+  }
+
+  private String[] getExceptions(DexAnnotationSet annotations, NamingLens namingLens) {
+    DexValue value = getSystemAnnotationValue(annotations, factory.annotationThrows);
+    if (value == null) {
+      return null;
+    }
+    DexValue[] values = value.asDexValueArray().getValues();
+    String[] res = new String[values.length];
+    for (int i = 0; i < values.length; i++) {
+      res[i] = namingLens.lookupInternalName(values[i].asDexValueType().value);
+    }
+    return res;
+  }
+
+  private Object getStaticValue(DexEncodedField field) {
+    if (!field.accessFlags.isStatic() || !field.hasExplicitStaticValue()) {
+      return null;
+    }
+    return field.getStaticValue().asAsmEncodedObject();
+  }
+
+  private void writeField(ProgramField field, ClassWriter writer) {
+    int access = field.getAccessFlags().getAsCfAccessFlags();
+    if (field.getDefinition().isDeprecated()) {
+      access = AsmUtils.withDeprecated(access);
+    }
+    String name = namingLens.lookupName(field.getReference()).toString();
+    String desc = namingLens.lookupDescriptor(field.getReference().type).toString();
+    String signature =
+        field
+            .getDefinition()
+            .getGenericSignature()
+            .toRenamedString(namingLens, this::isTypeMissing);
+    Object value = getStaticValue(field.getDefinition());
+    FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
+    writeAnnotations(
+        visitor::visitAnnotation,
+        visitor::visitTypeAnnotation,
+        field.getAnnotations().getAnnotations());
+    visitor.visitEnd();
+  }
+
+  private void writeMethod(
+      ProgramMethod method,
+      CfVersion classFileVersion,
+      LensCodeRewriterUtils rewriter,
+      ClassWriter writer,
+      ImmutableMap<DexString, DexValue> defaults) {
+    DexEncodedMethod definition = method.getDefinition();
+    int access = definition.getAccessFlags().getAsCfAccessFlags();
+    if (definition.isDeprecated()) {
+      access = AsmUtils.withDeprecated(access);
+    }
+    String name = namingLens.lookupName(method.getReference()).toString();
+    String desc = definition.descriptor(namingLens);
+    String signature =
+        method
+            .getDefinition()
+            .getGenericSignature()
+            .toRenamedString(namingLens, this::isTypeMissing);
+    String[] exceptions = getExceptions(definition.annotations(), namingLens);
+    MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
+    if (defaults.containsKey(definition.getName())) {
+      AnnotationVisitor defaultVisitor = visitor.visitAnnotationDefault();
+      if (defaultVisitor != null) {
+        writeAnnotationElement(defaultVisitor, null, defaults.get(definition.getName()));
+        defaultVisitor.visitEnd();
+      }
+    }
+    writeMethodParametersAnnotation(visitor, definition.annotations().annotations);
+    writeAnnotations(
+        visitor::visitAnnotation,
+        visitor::visitTypeAnnotation,
+        definition.annotations().annotations);
+    writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
+    if (!definition.shouldNotHaveCode()) {
+      writeCode(method, classFileVersion, namingLens, rewriter, visitor);
+    }
+    visitor.visitEnd();
+  }
+
+  @SuppressWarnings("ReferenceEquality")
+  private void writeMethodParametersAnnotation(MethodVisitor visitor, DexAnnotation[] annotations) {
+    for (DexAnnotation annotation : annotations) {
+      if (annotation.annotation.type == factory.annotationMethodParameters) {
+        assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
+        assert annotation.annotation.elements.length == 2;
+        assert annotation.annotation.elements[0].name.toString().equals("names");
+        assert annotation.annotation.elements[1].name.toString().equals("accessFlags");
+        DexValueArray names = annotation.annotation.elements[0].value.asDexValueArray();
+        DexValueArray accessFlags = annotation.annotation.elements[1].value.asDexValueArray();
+        assert names != null && accessFlags != null;
+        assert names.getValues().length == accessFlags.getValues().length;
+        for (int i = 0; i < names.getValues().length; i++) {
+          DexValueString name = names.getValues()[i].asDexValueString();
+          DexValueInt access = accessFlags.getValues()[i].asDexValueInt();
+          String nameString = name != null ? name.value.toString() : null;
+          visitor.visitParameter(nameString, access.value);
+        }
+      }
+    }
+  }
+
+  private void writeParameterAnnotations(
+      MethodVisitor visitor, ParameterAnnotationsList parameterAnnotations) {
+    // TODO(113565942): We currently assume that the annotable parameter count
+    // it the same for visible and invisible annotations. That doesn't actually
+    // seem to be the case in the class file format.
+    visitor.visitAnnotableParameterCount(parameterAnnotations.getAnnotableParameterCount(), true);
+    visitor.visitAnnotableParameterCount(parameterAnnotations.getAnnotableParameterCount(), false);
+    for (int i = 0; i < parameterAnnotations.size(); i++) {
+      int iFinal = i;
+      writeAnnotations(
+          (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
+          (typeRef, typePath, desc, visible) -> {
+            throw new Unreachable("Type annotations are not placed on parameters");
+          },
+          parameterAnnotations.get(i).annotations);
+    }
+  }
+
+  private void writeAnnotations(
+      AnnotationConsumer visitor,
+      TypeAnnotationConsumer typeAnnotationVisitor,
+      DexAnnotation[] annotations) {
+    for (DexAnnotation dexAnnotation : annotations) {
+      if (dexAnnotation.visibility == DexAnnotation.VISIBILITY_SYSTEM) {
+        // Annotations with VISIBILITY_SYSTEM are not annotations in CF, but are special
+        // annotations in Dex, i.e. default, enclosing class, enclosing method, member classes,
+        // signature, throws.
+        continue;
+      }
+      String desc = namingLens.lookupDescriptor(dexAnnotation.annotation.type).toString();
+      boolean visible = dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME;
+      DexTypeAnnotation dexTypeAnnotation = dexAnnotation.asTypeAnnotation();
+      AnnotationVisitor v =
+          dexTypeAnnotation == null
+              ? visitor.visit(desc, visible)
+              : typeAnnotationVisitor.visit(
+                  dexTypeAnnotation.getTypeRef(), dexTypeAnnotation.getTypePath(), desc, visible);
+      if (v != null) {
+        writeAnnotation(v, dexAnnotation.annotation);
+        v.visitEnd();
+      }
+    }
+  }
+
+  private void writeAnnotation(AnnotationVisitor v, DexEncodedAnnotation annotation) {
+    for (DexAnnotationElement element : annotation.elements) {
+      writeAnnotationElement(v, element.name.toString(), element.value);
+    }
+  }
+
+  @SuppressWarnings("ReferenceEquality")
+  private void writeAnnotationElement(AnnotationVisitor visitor, String name, DexValue value) {
+    switch (value.getValueKind()) {
+      case ANNOTATION:
+        {
+          DexValueAnnotation valueAnnotation = value.asDexValueAnnotation();
+          AnnotationVisitor innerVisitor =
+              visitor.visitAnnotation(
+                  name, namingLens.lookupDescriptor(valueAnnotation.value.type).toString());
+          if (innerVisitor != null) {
+            writeAnnotation(innerVisitor, valueAnnotation.value);
+            innerVisitor.visitEnd();
+          }
+        }
+        break;
+
+      case ARRAY:
+        {
+          DexValue[] values = value.asDexValueArray().getValues();
+          AnnotationVisitor innerVisitor = visitor.visitArray(name);
+          if (innerVisitor != null) {
+            for (DexValue elementValue : values) {
+              writeAnnotationElement(innerVisitor, null, elementValue);
+            }
+            innerVisitor.visitEnd();
+          }
+        }
+        break;
+
+      case ENUM:
+        DexField enumField = value.asDexValueEnum().getValue();
+        // This must not be renamed, as the Java runtime will use Enum.valueOf to find the enum's
+        // referenced in annotations. See b/236691999 for details.
+        assert namingLens.lookupName(enumField) == enumField.name
+                || System.getProperty("com.android.tools.r8.tracereferences.obfuscateAllEnums")
+                    != null
+            : "Enum field " + enumField.name + " renamed to " + namingLens.lookupName(enumField);
+        visitor.visitEnum(
+            name,
+            namingLens.lookupDescriptor(enumField.getType()).toString(),
+            enumField.name.toString());
+        break;
+
+      case FIELD:
+        throw new Unreachable("writeAnnotationElement of DexValueField");
+
+      case METHOD:
+        throw new Unreachable("writeAnnotationElement of DexValueMethod");
+
+      case METHOD_HANDLE:
+        throw new Unreachable("writeAnnotationElement of DexValueMethodHandle");
+
+      case METHOD_TYPE:
+        throw new Unreachable("writeAnnotationElement of DexValueMethodType");
+
+      case STRING:
+        visitor.visit(name, value.asDexValueString().getValue().toString());
+        break;
+
+      case TYPE:
+        visitor.visit(
+            name,
+            Type.getType(namingLens.lookupDescriptor(value.asDexValueType().value).toString()));
+        break;
+
+      default:
+        visitor.visit(name, value.getBoxedValue());
+        break;
+    }
+  }
+
+  private void writeCode(
+      ProgramMethod method,
+      CfVersion classFileVersion,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    Code code = method.getDefinition().getCode();
+    assert code.isCfWritableCode();
+    assert code.estimatedDexCodeSizeUpperBoundInBytes() > 0;
+    if (!code.isCfWritableCode()) {
+      // This should never happen (see assertion above), but desugaring bugs may lead the
+      // CfApplicationWriter to try to write invalid code and we need the better error message.
+      throw new Unreachable(
+          "The CfApplicationWriter cannot write non cf writable code "
+              + code.getClass().getCanonicalName()
+              + " for method "
+              + method.getReference().toSourceString());
+    }
+    code.asCfWritableCode()
+        .writeCf(method, classFileVersion, appView, namingLens, rewriter, visitor);
+  }
+
+  private int compareTypesThroughLens(DexType a, DexType b) {
+    return namingLens.lookupDescriptor(a).compareTo(namingLens.lookupDescriptor(b));
+  }
+
+  private DexString returnTypeThroughLens(DexMethod method) {
+    return namingLens.lookupDescriptor(method.getReturnType());
+  }
+
+  private int compareMethodsThroughLens(ProgramMethod a, ProgramMethod b) {
+    // When writing class files, methods are only compared within the same class.
+    assert a.getHolder().equals(b.getHolder());
+    return Comparator.comparing(this::returnTypeThroughLens)
+        .thenComparing(DexMethod::getName)
+        // .thenComparingInt(m -> m.getProto().getArity()) // Done in arrayComp below.
+        .thenComparing(
+            m -> m.getProto().parameters.values,
+            ComparatorUtils.arrayComparator(this::compareTypesThroughLens))
+        .compare(a.getReference(), b.getReference());
+  }
+
+  private CfVersion getClassFileVersion(DexEncodedMethod method) {
+    if (!method.hasClassFileVersion()) {
+      // In this case bridges have been introduced for the Cf back-end,
+      // which do not have class file version.
+      assert options.getLibraryDesugaringOptions().isDesugaredLibraryCompilation()
+              || options.isDesugaring()
+          : "Expected class file version for " + method.getReference().toSourceString();
+      assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(
+          options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION));
+      // Any desugaring rewrites which cannot meet the default class file version after
+      // desugaring must upgrade the class file version during desugaring.
+      return options.isDesugaring()
+          ? options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION)
+          : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
+    }
+    return method.getClassFileVersion();
+  }
+
+  private CfVersion getClassFileVersion(DexProgramClass clazz) {
+    CfVersion version =
+        clazz.hasClassFileVersion()
+            ? clazz.getInitialClassFileVersion()
+            : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
+    for (DexEncodedMethod method : clazz.directMethods()) {
+      version = Ordered.max(version, getClassFileVersion(method));
+    }
+    for (DexEncodedMethod method : clazz.virtualMethods()) {
+      version = Ordered.max(version, getClassFileVersion(method));
+    }
+    return version;
+  }
+
+  private DexValue getSystemAnnotationValue(DexAnnotationSet annotations, DexType type) {
+    DexAnnotation annotation = annotations.getFirstMatching(type);
+    if (annotation == null) {
+      return null;
+    }
+    assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
+    DexEncodedAnnotation encodedAnnotation = annotation.annotation;
+    assert encodedAnnotation.elements.length == 1;
+    return encodedAnnotation.elements[0].value;
+  }
+
+  private interface AnnotationConsumer {
+    AnnotationVisitor visit(String desc, boolean visible);
+  }
+
+  private interface TypeAnnotationConsumer {
+    AnnotationVisitor visit(int typeRef, TypePath typePath, String desc, boolean visible);
+  }
+
+  public static String printCf(byte[] result) {
+    ClassReader reader = new ClassReader(result);
+    ClassNode node = new ClassNode(ASM_VERSION);
+    reader.accept(node, ASM_VERSION);
+    StringWriter writer = new StringWriter();
+    for (MethodNode method : node.methods) {
+      writer.append(method.name).append(method.desc).append('\n');
+      TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
+      method.accept(visitor);
+      visitor.p.print(new PrintWriter(writer));
+      writer.append('\n');
+    }
+    return writer.toString();
+  }
+
+  @SuppressWarnings("DefaultCharset")
+  private static void verifyCf(byte[] result) {
+    ClassReader reader = new ClassReader(result);
+    PrintWriter pw = new PrintWriter(System.out);
+    CheckClassAdapter.verify(reader, false, pw);
+  }
+}
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 dc57f39..70b8e56 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -3,115 +3,41 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jar;
 
-import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 import static com.android.tools.r8.utils.positions.LineNumberOptimizer.runAndWriteMap;
 
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.SourceFileEnvironment;
-import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.debuginfo.DebugRepresentation;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.errors.CodeSizeOverflowDiagnostic;
-import com.android.tools.r8.errors.ConstantPoolOverflowDiagnostic;
-import com.android.tools.r8.errors.Unreachable;
 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;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeAnnotation;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.NestMemberClassAttribute;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.PermittedSubclassAttribute;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.RecordComponentInfo;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AsmUtils;
-import com.android.tools.r8.utils.ComparatorUtils;
-import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsCfConsumer;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OriginalSourceFiles;
-import com.android.tools.r8.utils.PredicateUtils;
-import com.android.tools.r8.utils.structural.Ordered;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMap.Builder;
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassTooLargeException;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodTooLargeException;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.TypePath;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.util.CheckClassAdapter;
-import org.objectweb.asm.util.Textifier;
-import org.objectweb.asm.util.TraceMethodVisitor;
 
 public class CfApplicationWriter {
 
-  private static final boolean RUN_VERIFIER = false;
-  private static final boolean PRINT_CF = false;
-
-  // First item inserted into the constant pool is the marker string which generates an UTF8 to
-  // pool index #1 and a String entry to #2, referencing #1.
-  public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
-
   private final DexApplication application;
   private final AppView<?> appView;
   private final InternalOptions options;
   private final Optional<Marker> marker;
-  private final Predicate<DexType> isTypeMissing;
-
-  private static final CfVersion MIN_VERSION_FOR_COMPILER_GENERATED_CODE = CfVersion.V1_6;
 
   public CfApplicationWriter(AppView<?> appView, Marker marker) {
     this.application = appView.appInfo().app();
     this.appView = appView;
     this.options = appView.options();
     this.marker = Optional.ofNullable(marker);
-    this.isTypeMissing =
-        PredicateUtils.isNull(appView.appInfo()::definitionForWithoutExistenceAssert);
-  }
-
-  private NamingLens getNamingLens() {
-    return appView.getNamingLens();
   }
 
   public void write(ClassFileConsumer consumer, ExecutorService executorService) {
@@ -179,536 +105,19 @@
       }
     }
     for (DexProgramClass clazz : classes) {
-      writeClassCatchingErrors(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
+      new CfApplicationClassWriter(appView, clazz)
+          .writeClassCatchingErrors(consumer, rewriter, markerString, sourceFileEnvironment);
     }
     if (!globalSyntheticClasses.isEmpty()) {
       InternalGlobalSyntheticsCfConsumer globalsConsumer =
           new InternalGlobalSyntheticsCfConsumer(options.getGlobalSyntheticsConsumer(), appView);
       for (DexProgramClass clazz : globalSyntheticClasses) {
-        writeClassCatchingErrors(
-            clazz, globalsConsumer, rewriter, markerString, sourceFileEnvironment);
+        new CfApplicationClassWriter(appView, clazz)
+            .writeClassCatchingErrors(
+                globalsConsumer, rewriter, markerString, sourceFileEnvironment);
       }
       globalsConsumer.finished(appView);
     }
     ApplicationWriter.supplyAdditionalConsumers(appView, executorService, Collections.emptyList());
   }
-
-  private void writeClassCatchingErrors(
-      DexProgramClass clazz,
-      ClassFileConsumer consumer,
-      LensCodeRewriterUtils rewriter,
-      Optional<String> markerString,
-      SourceFileEnvironment sourceFileEnvironment) {
-    assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
-    try {
-      writeClass(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
-    } catch (ClassTooLargeException e) {
-      throw appView
-          .options()
-          .reporter
-          .fatalError(
-              new ConstantPoolOverflowDiagnostic(
-                  clazz.getOrigin(),
-                  Reference.classFromBinaryName(e.getClassName()),
-                  e.getConstantPoolCount()));
-    } catch (MethodTooLargeException e) {
-      throw appView
-          .options()
-          .reporter
-          .fatalError(
-              new CodeSizeOverflowDiagnostic(
-                  clazz.getOrigin(),
-                  Reference.methodFromDescriptor(
-                      Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
-                      e.getMethodName(),
-                      e.getDescriptor()),
-                  e.getCodeSize()));
-    }
-  }
-
-  private void writeClass(
-      DexProgramClass clazz,
-      ClassFileConsumer consumer,
-      LensCodeRewriterUtils rewriter,
-      Optional<String> markerString,
-      SourceFileEnvironment sourceFileEnvironment) {
-    ClassWriter writer = new ClassWriter(0);
-    if (markerString.isPresent()) {
-      int markerStringPoolIndex = writer.newConst(markerString.get());
-      assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
-    }
-    String sourceFile;
-    if (options.sourceFileProvider == null) {
-      sourceFile = clazz.sourceFile != null ? clazz.sourceFile.toString() : null;
-    } else {
-      sourceFile = options.sourceFileProvider.get(sourceFileEnvironment);
-    }
-    String sourceDebug = getSourceDebugExtension(clazz.annotations());
-    writer.visitSource(sourceFile, sourceDebug);
-    CfVersion version = getClassFileVersion(clazz);
-    if (version.isGreaterThanOrEqualTo(CfVersion.V1_8)) {
-      // JDK8 and after ignore ACC_SUPER so unset it.
-      clazz.accessFlags.unsetSuper();
-    } else {
-      // In all other cases set the super bit as D8/R8 do not support targeting pre 1.0.2 JDKs.
-      if (!clazz.accessFlags.isInterface()) {
-        clazz.accessFlags.setSuper();
-      }
-    }
-    boolean allowInvalidCfAccessFlags = false;
-    if (clazz
-        .getType()
-        .getDescriptor()
-        .endsWith(appView.dexItemFactory().createString("/package-info;"))) {
-      allowInvalidCfAccessFlags = true;
-    }
-    int access =
-        allowInvalidCfAccessFlags || options.testing.allowInvalidCfAccessFlags
-            ? clazz.accessFlags.materialize()
-            : clazz.accessFlags.getAsCfAccessFlags();
-    if (clazz.isDeprecated()) {
-      access = AsmUtils.withDeprecated(access);
-    }
-    String desc = getNamingLens().lookupDescriptor(clazz.type).toString();
-    String name = getNamingLens().lookupInternalName(clazz.type);
-    String signature = clazz.getClassSignature().toRenamedString(getNamingLens(), isTypeMissing);
-    String superName =
-        clazz.hasSuperType() ? getNamingLens().lookupInternalName(clazz.superType) : null;
-    String[] interfaces = new String[clazz.interfaces.values.length];
-    for (int i = 0; i < clazz.interfaces.values.length; i++) {
-      interfaces[i] = getNamingLens().lookupInternalName(clazz.interfaces.values[i]);
-    }
-    assert SyntheticNaming.verifyNotInternalSynthetic(name);
-    writer.visit(version.raw(), access, name, signature, superName, interfaces);
-    appView.getSyntheticItems().writeAttributeIfIntermediateSyntheticClass(writer, clazz, appView);
-    writeAnnotations(
-        writer::visitAnnotation, writer::visitTypeAnnotation, clazz.annotations().annotations);
-    ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
-
-    if (clazz.getEnclosingMethodAttribute() != null) {
-      clazz.getEnclosingMethodAttribute().write(writer, getNamingLens());
-    }
-
-    if (clazz.getNestHostClassAttribute() != null) {
-      clazz.getNestHostClassAttribute().write(writer, getNamingLens());
-    }
-
-    for (NestMemberClassAttribute entry : clazz.getNestMembersClassAttributes()) {
-      entry.write(writer, getNamingLens());
-      assert clazz.getNestHostClassAttribute() == null
-          : "A nest host cannot also be a nest member.";
-    }
-
-    for (PermittedSubclassAttribute entry : clazz.getPermittedSubclassAttributes()) {
-      entry.write(writer, getNamingLens());
-    }
-
-    if (clazz.isRecord()) {
-      // TODO(b/274888318): Strip record components if not kept.
-      for (RecordComponentInfo info : clazz.getRecordComponents()) {
-        info.write(writer, getNamingLens(), isTypeMissing, this::writeAnnotation);
-      }
-    }
-
-    for (InnerClassAttribute entry : clazz.getInnerClasses()) {
-      entry.write(writer, getNamingLens(), options);
-    }
-
-    for (DexEncodedField field : clazz.staticFields()) {
-      writeField(field, writer);
-    }
-    for (DexEncodedField field : clazz.instanceFields()) {
-      writeField(field, writer);
-    }
-    if (options.desugarSpecificOptions().sortMethodsOnCfOutput) {
-      List<ProgramMethod> programMethodSorted = new ArrayList<>();
-      clazz.forEachProgramMethod(programMethodSorted::add);
-      programMethodSorted.sort(this::compareMethodsThroughLens);
-      programMethodSorted.forEach(
-          method -> writeMethod(method, version, rewriter, writer, defaults));
-    } else {
-      clazz.forEachProgramMethod(
-          method -> writeMethod(method, version, rewriter, writer, defaults));
-    }
-    writer.visitEnd();
-
-    byte[] result = writer.toByteArray();
-    if (PRINT_CF) {
-      System.out.print(printCf(result));
-      System.out.flush();
-    }
-    if (RUN_VERIFIER) {
-      // Generally, this will fail with ClassNotFoundException,
-      // so don't assert that verifyCf() returns true.
-      verifyCf(result);
-    }
-    ExceptionUtils.withConsumeResourceHandler(
-        options.reporter, handler -> consumer.accept(ByteDataView.of(result), desc, handler));
-  }
-
-  private int compareTypesThroughLens(DexType a, DexType b) {
-    return getNamingLens().lookupDescriptor(a).compareTo(getNamingLens().lookupDescriptor(b));
-  }
-
-  private DexString returnTypeThroughLens(DexMethod method) {
-    return getNamingLens().lookupDescriptor(method.getReturnType());
-  }
-
-  private int compareMethodsThroughLens(ProgramMethod a, ProgramMethod b) {
-    // When writing class files, methods are only compared within the same class.
-    assert a.getHolder().equals(b.getHolder());
-    return Comparator.comparing(this::returnTypeThroughLens)
-        .thenComparing(DexMethod::getName)
-        // .thenComparingInt(m -> m.getProto().getArity()) // Done in arrayComp below.
-        .thenComparing(
-            m -> m.getProto().parameters.values,
-            ComparatorUtils.arrayComparator(this::compareTypesThroughLens))
-        .compare(a.getReference(), b.getReference());
-  }
-
-  private CfVersion getClassFileVersion(DexEncodedMethod method) {
-    if (!method.hasClassFileVersion()) {
-      // In this case bridges have been introduced for the Cf back-end,
-      // which do not have class file version.
-      assert options.getLibraryDesugaringOptions().isDesugaredLibraryCompilation()
-              || options.isDesugaring()
-          : "Expected class file version for " + method.getReference().toSourceString();
-      assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(
-          options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION));
-      // Any desugaring rewrites which cannot meet the default class file version after
-      // desugaring must upgrade the class file version during desugaring.
-      return options.isDesugaring()
-          ? options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION)
-          : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
-    }
-    return method.getClassFileVersion();
-  }
-
-  private CfVersion getClassFileVersion(DexProgramClass clazz) {
-    CfVersion version =
-        clazz.hasClassFileVersion()
-            ? clazz.getInitialClassFileVersion()
-            : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
-    for (DexEncodedMethod method : clazz.directMethods()) {
-      version = Ordered.max(version, getClassFileVersion(method));
-    }
-    for (DexEncodedMethod method : clazz.virtualMethods()) {
-      version = Ordered.max(version, getClassFileVersion(method));
-    }
-    return version;
-  }
-
-  private DexValue getSystemAnnotationValue(DexAnnotationSet annotations, DexType type) {
-    DexAnnotation annotation = annotations.getFirstMatching(type);
-    if (annotation == null) {
-      return null;
-    }
-    assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
-    DexEncodedAnnotation encodedAnnotation = annotation.annotation;
-    assert encodedAnnotation.elements.length == 1;
-    return encodedAnnotation.elements[0].value;
-  }
-
-  private String getSourceDebugExtension(DexAnnotationSet annotations) {
-    DexValue debugExtensions =
-        getSystemAnnotationValue(
-            annotations, application.dexItemFactory.annotationSourceDebugExtension);
-    if (debugExtensions == null) {
-      return null;
-    }
-    return debugExtensions.asDexValueString().getValue().toString();
-  }
-
-  @SuppressWarnings("BadImport")
-  private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
-    DexValue value =
-        getSystemAnnotationValue(annotations, application.dexItemFactory.annotationDefault);
-    if (value == null) {
-      return ImmutableMap.of();
-    }
-    DexEncodedAnnotation annotation = value.asDexValueAnnotation().value;
-    Builder<DexString, DexValue> builder = ImmutableMap.builder();
-    for (DexAnnotationElement element : annotation.elements) {
-      builder.put(element.name, element.value);
-    }
-    return builder.build();
-  }
-
-  private String[] getExceptions(DexAnnotationSet annotations) {
-    DexValue value =
-        getSystemAnnotationValue(annotations, application.dexItemFactory.annotationThrows);
-    if (value == null) {
-      return null;
-    }
-    DexValue[] values = value.asDexValueArray().getValues();
-    String[] res = new String[values.length];
-    for (int i = 0; i < values.length; i++) {
-      res[i] = getNamingLens().lookupInternalName(values[i].asDexValueType().value);
-    }
-    return res;
-  }
-
-  private Object getStaticValue(DexEncodedField field) {
-    if (!field.accessFlags.isStatic() || !field.hasExplicitStaticValue()) {
-      return null;
-    }
-    return field.getStaticValue().asAsmEncodedObject();
-  }
-
-  private void writeField(DexEncodedField field, ClassWriter writer) {
-    int access = field.accessFlags.getAsCfAccessFlags();
-    if (field.isDeprecated()) {
-      access = AsmUtils.withDeprecated(access);
-    }
-    String name = getNamingLens().lookupName(field.getReference()).toString();
-    String desc = getNamingLens().lookupDescriptor(field.getReference().type).toString();
-    String signature = field.getGenericSignature().toRenamedString(getNamingLens(), isTypeMissing);
-    Object value = getStaticValue(field);
-    FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
-    writeAnnotations(
-        visitor::visitAnnotation, visitor::visitTypeAnnotation, field.annotations().annotations);
-    visitor.visitEnd();
-  }
-
-  private void writeMethod(
-      ProgramMethod method,
-      CfVersion classFileVersion,
-      LensCodeRewriterUtils rewriter,
-      ClassWriter writer,
-      ImmutableMap<DexString, DexValue> defaults) {
-    // For "pass through" classes which has already been library desugared use the identity lens.
-    NamingLens namingLens =
-        appView.isAlreadyLibraryDesugared(method.getHolder())
-            ? NamingLens.getIdentityLens()
-            : getNamingLens();
-    DexEncodedMethod definition = method.getDefinition();
-    int access = definition.getAccessFlags().getAsCfAccessFlags();
-    if (definition.isDeprecated()) {
-      access = AsmUtils.withDeprecated(access);
-    }
-    String name = namingLens.lookupName(method.getReference()).toString();
-    String desc = definition.descriptor(namingLens);
-    String signature =
-        method.getDefinition().getGenericSignature().toRenamedString(namingLens, isTypeMissing);
-    String[] exceptions = getExceptions(definition.annotations());
-    MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
-    if (defaults.containsKey(definition.getName())) {
-      AnnotationVisitor defaultVisitor = visitor.visitAnnotationDefault();
-      if (defaultVisitor != null) {
-        writeAnnotationElement(defaultVisitor, null, defaults.get(definition.getName()));
-        defaultVisitor.visitEnd();
-      }
-    }
-    writeMethodParametersAnnotation(visitor, definition.annotations().annotations);
-    writeAnnotations(
-        visitor::visitAnnotation,
-        visitor::visitTypeAnnotation,
-        definition.annotations().annotations);
-    writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
-    if (!definition.shouldNotHaveCode()) {
-      writeCode(method, classFileVersion, namingLens, rewriter, visitor);
-    }
-    visitor.visitEnd();
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private void writeMethodParametersAnnotation(MethodVisitor visitor, DexAnnotation[] annotations) {
-    for (DexAnnotation annotation : annotations) {
-      if (annotation.annotation.type == appView.dexItemFactory().annotationMethodParameters) {
-        assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
-        assert annotation.annotation.elements.length == 2;
-        assert annotation.annotation.elements[0].name.toString().equals("names");
-        assert annotation.annotation.elements[1].name.toString().equals("accessFlags");
-        DexValueArray names = annotation.annotation.elements[0].value.asDexValueArray();
-        DexValueArray accessFlags = annotation.annotation.elements[1].value.asDexValueArray();
-        assert names != null && accessFlags != null;
-        assert names.getValues().length == accessFlags.getValues().length;
-        for (int i = 0; i < names.getValues().length; i++) {
-          DexValueString name = names.getValues()[i].asDexValueString();
-          DexValueInt access = accessFlags.getValues()[i].asDexValueInt();
-          String nameString = name != null ? name.value.toString() : null;
-          visitor.visitParameter(nameString, access.value);
-        }
-      }
-    }
-  }
-
-  private void writeParameterAnnotations(
-      MethodVisitor visitor, ParameterAnnotationsList parameterAnnotations) {
-    // TODO(113565942): We currently assume that the annotable parameter count
-    // it the same for visible and invisible annotations. That doesn't actually
-    // seem to be the case in the class file format.
-    visitor.visitAnnotableParameterCount(
-        parameterAnnotations.getAnnotableParameterCount(), true);
-    visitor.visitAnnotableParameterCount(
-        parameterAnnotations.getAnnotableParameterCount(), false);
-    for (int i = 0; i < parameterAnnotations.size(); i++) {
-      int iFinal = i;
-      writeAnnotations(
-          (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
-          (typeRef, typePath, desc, visible) -> {
-            throw new Unreachable("Type annotations are not placed on parameters");
-          },
-          parameterAnnotations.get(i).annotations);
-    }
-  }
-
-  private interface AnnotationConsumer {
-    AnnotationVisitor visit(String desc, boolean visible);
-  }
-
-  private interface TypeAnnotationConsumer {
-    AnnotationVisitor visit(int typeRef, TypePath typePath, String desc, boolean visible);
-  }
-
-  private void writeAnnotations(
-      AnnotationConsumer visitor,
-      TypeAnnotationConsumer typeAnnotationVisitor,
-      DexAnnotation[] annotations) {
-    for (DexAnnotation dexAnnotation : annotations) {
-      if (dexAnnotation.visibility == DexAnnotation.VISIBILITY_SYSTEM) {
-        // Annotations with VISIBILITY_SYSTEM are not annotations in CF, but are special
-        // annotations in Dex, i.e. default, enclosing class, enclosing method, member classes,
-        // signature, throws.
-        continue;
-      }
-      String desc = getNamingLens().lookupDescriptor(dexAnnotation.annotation.type).toString();
-      boolean visible = dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME;
-      DexTypeAnnotation dexTypeAnnotation = dexAnnotation.asTypeAnnotation();
-      AnnotationVisitor v =
-          dexTypeAnnotation == null
-              ? visitor.visit(desc, visible)
-              : typeAnnotationVisitor.visit(
-                  dexTypeAnnotation.getTypeRef(), dexTypeAnnotation.getTypePath(), desc, visible);
-      if (v != null) {
-        writeAnnotation(v, dexAnnotation.annotation);
-        v.visitEnd();
-      }
-    }
-  }
-
-  private void writeAnnotation(AnnotationVisitor v, DexEncodedAnnotation annotation) {
-    for (DexAnnotationElement element : annotation.elements) {
-      writeAnnotationElement(v, element.name.toString(), element.value);
-    }
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private void writeAnnotationElement(AnnotationVisitor visitor, String name, DexValue value) {
-    switch (value.getValueKind()) {
-      case ANNOTATION:
-        {
-          DexValueAnnotation valueAnnotation = value.asDexValueAnnotation();
-          AnnotationVisitor innerVisitor =
-              visitor.visitAnnotation(
-                  name, getNamingLens().lookupDescriptor(valueAnnotation.value.type).toString());
-          if (innerVisitor != null) {
-            writeAnnotation(innerVisitor, valueAnnotation.value);
-            innerVisitor.visitEnd();
-          }
-        }
-        break;
-
-      case ARRAY:
-        {
-          DexValue[] values = value.asDexValueArray().getValues();
-          AnnotationVisitor innerVisitor = visitor.visitArray(name);
-          if (innerVisitor != null) {
-            for (DexValue elementValue : values) {
-              writeAnnotationElement(innerVisitor, null, elementValue);
-            }
-            innerVisitor.visitEnd();
-          }
-        }
-        break;
-
-      case ENUM:
-        DexField enumField = value.asDexValueEnum().getValue();
-        // This must not be renamed, as the Java runtime will use Enum.valueOf to find the enum's
-        // referenced in annotations. See b/236691999 for details.
-        assert getNamingLens().lookupName(enumField) == enumField.name
-                || System.getProperty("com.android.tools.r8.tracereferences.obfuscateAllEnums")
-                    != null
-            : "Enum field "
-                + enumField.name
-                + " renamed to "
-                + getNamingLens().lookupName(enumField);
-        visitor.visitEnum(
-            name,
-            getNamingLens().lookupDescriptor(enumField.getType()).toString(),
-            enumField.name.toString());
-        break;
-
-      case FIELD:
-        throw new Unreachable("writeAnnotationElement of DexValueField");
-
-      case METHOD:
-        throw new Unreachable("writeAnnotationElement of DexValueMethod");
-
-      case METHOD_HANDLE:
-        throw new Unreachable("writeAnnotationElement of DexValueMethodHandle");
-
-      case METHOD_TYPE:
-        throw new Unreachable("writeAnnotationElement of DexValueMethodType");
-
-      case STRING:
-        visitor.visit(name, value.asDexValueString().getValue().toString());
-        break;
-
-      case TYPE:
-        visitor.visit(
-            name,
-            Type.getType(
-                getNamingLens().lookupDescriptor(value.asDexValueType().value).toString()));
-        break;
-
-      default:
-        visitor.visit(name, value.getBoxedValue());
-        break;
-    }
-  }
-
-  private void writeCode(
-      ProgramMethod method,
-      CfVersion classFileVersion,
-      NamingLens namingLens,
-      LensCodeRewriterUtils rewriter,
-      MethodVisitor visitor) {
-    Code code = method.getDefinition().getCode();
-    assert code.isCfWritableCode();
-    assert code.estimatedDexCodeSizeUpperBoundInBytes() > 0;
-    if (!code.isCfWritableCode()) {
-      // This should never happen (see assertion above), but desugaring bugs may lead the
-      // CfApplicationWriter to try to write invalid code and we need the better error message.
-      throw new Unreachable(
-          "The CfApplicationWriter cannot write non cf writable code "
-              + code.getClass().getCanonicalName()
-              + " for method "
-              + method.getReference().toSourceString());
-    }
-    code.asCfWritableCode()
-        .writeCf(method, classFileVersion, appView, namingLens, rewriter, visitor);
-  }
-
-  public static String printCf(byte[] result) {
-    ClassReader reader = new ClassReader(result);
-    ClassNode node = new ClassNode(ASM_VERSION);
-    reader.accept(node, ASM_VERSION);
-    StringWriter writer = new StringWriter();
-    for (MethodNode method : node.methods) {
-      writer.append(method.name).append(method.desc).append('\n');
-      TraceMethodVisitor visitor = new TraceMethodVisitor(new Textifier());
-      method.accept(visitor);
-      visitor.p.print(new PrintWriter(writer));
-      writer.append('\n');
-    }
-    return writer.toString();
-  }
-
-  @SuppressWarnings("DefaultCharset")
-  private static void verifyCf(byte[] result) {
-    ClassReader reader = new ClassReader(result);
-    PrintWriter pw = new PrintWriter(System.out);
-    CheckClassAdapter.verify(reader, false, pw);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index d6028ef..64fb5dd 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -148,6 +148,11 @@
   }
 
   @Override
+  public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+    return this;
+  }
+
+  @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
     renaming.forEach(
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index 3e551cf..bc8b12f 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -184,6 +184,8 @@
     return true;
   }
 
+  public abstract NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens();
+
   public abstract static class NonIdentityNamingLens extends NamingLens {
 
     private final DexItemFactory dexItemFactory;
@@ -251,5 +253,10 @@
     public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
       return true;
     }
+
+    @Override
+    public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+      return this;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index d2f2e8a..78cabaa 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -559,7 +559,6 @@
     private final Set<DexReference> unmappedReferences;
     private final Map<DexString, DexType> classRenamingsMappingToDifferentName;
 
-    @SuppressWarnings("ReferenceEquality")
     ProguardMapMinifiedRenaming(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         ClassRenaming classRenaming,
@@ -571,7 +570,7 @@
       classRenamingsMappingToDifferentName = new HashMap<>();
       classRenaming.classRenaming.forEach(
           (type, dexString) -> {
-            if (type.descriptor != dexString) {
+            if (type.getDescriptor().isNotIdenticalTo(dexString)) {
               classRenamingsMappingToDifferentName.put(dexString, type);
             }
           });
diff --git a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
index 517377d..f946bd6 100644
--- a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
@@ -18,7 +18,6 @@
 // Naming lens for rewriting java.lang.Record to the internal RecordTag type.
 public class RecordRewritingNamingLens extends NonIdentityNamingLens {
 
-  private final DexItemFactory factory;
   private final NamingLens namingLens;
 
   public static void commitRecordRewritingNamingLens(AppView<?> appView) {
@@ -36,9 +35,12 @@
   }
 
   public RecordRewritingNamingLens(AppView<?> appView) {
-    super(appView.dexItemFactory());
-    this.factory = appView.dexItemFactory();
-    this.namingLens = appView.getNamingLens();
+    this(appView.dexItemFactory(), appView.getNamingLens());
+  }
+
+  public RecordRewritingNamingLens(DexItemFactory factory, NamingLens namingLens) {
+    super(factory);
+    this.namingLens = namingLens;
   }
 
   private boolean isRenamed(DexType type) {
@@ -47,8 +49,8 @@
 
   @SuppressWarnings("ReferenceEquality")
   private DexString getRenaming(DexType type) {
-    if (type == factory.recordType) {
-      return factory.recordTagType.descriptor;
+    if (type == dexItemFactory().recordType) {
+      return dexItemFactory().recordTagType.descriptor;
     }
     return null;
   }
@@ -96,4 +98,12 @@
   public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
     return namingLens.verifyRenamingConsistentWithResolution(item);
   }
+
+  @Override
+  public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+    NamingLens newParent = namingLens.withoutDesugaredLibraryPrefixRewritingNamingLens();
+    return newParent != namingLens
+        ? new RecordRewritingNamingLens(dexItemFactory(), newParent)
+        : this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java
index b72bb6b..13c0087 100644
--- a/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/VarHandleDesugaringRewritingNamingLens.java
@@ -20,7 +20,6 @@
 // to com.android.tools.r8.DesugarMethodHandlesLookup.
 public class VarHandleDesugaringRewritingNamingLens extends NonIdentityNamingLens {
 
-  private final DexItemFactory factory;
   private final NamingLens namingLens;
   private final Map<DexType, DexString> mapping;
 
@@ -93,9 +92,13 @@
 
   private VarHandleDesugaringRewritingNamingLens(
       AppView<?> appView, Map<DexType, DexString> mapping) {
-    super(appView.dexItemFactory());
-    this.factory = appView.dexItemFactory();
-    this.namingLens = appView.getNamingLens();
+    this(appView.dexItemFactory(), appView.getNamingLens(), mapping);
+  }
+
+  private VarHandleDesugaringRewritingNamingLens(
+      DexItemFactory factory, NamingLens namingLens, Map<DexType, DexString> mapping) {
+    super(factory);
+    this.namingLens = namingLens;
     this.mapping = mapping;
   }
 
@@ -105,8 +108,8 @@
 
   @SuppressWarnings("ReferenceEquality")
   private DexString getRenaming(DexType type) {
-    assert type != factory.desugarMethodHandlesLookupType;
-    assert type != factory.desugarVarHandleType;
+    assert type != dexItemFactory().desugarMethodHandlesLookupType;
+    assert type != dexItemFactory().desugarVarHandleType;
     return mapping.get(type);
   }
 
@@ -153,4 +156,12 @@
   public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
     return namingLens.verifyRenamingConsistentWithResolution(item);
   }
+
+  @Override
+  public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+    NamingLens newParent = namingLens.withoutDesugaredLibraryPrefixRewritingNamingLens();
+    return newParent != namingLens
+        ? new VarHandleDesugaringRewritingNamingLens(dexItemFactory(), newParent, mapping)
+        : this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java b/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java
index 40726bc..c3896f9 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java
@@ -4,28 +4,27 @@
 package com.android.tools.r8.partial;
 
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.partial.predicate.AllClassesMatcher;
+import com.android.tools.r8.partial.predicate.ClassNameMatcher;
+import com.android.tools.r8.partial.predicate.ClassPrefixMatcher;
+import com.android.tools.r8.partial.predicate.PackageAndSubpackagePrefixMatcher;
+import com.android.tools.r8.partial.predicate.PackagePrefixMatcher;
+import com.android.tools.r8.partial.predicate.R8PartialPredicate;
+import com.android.tools.r8.partial.predicate.R8PartialPredicateCollection;
+import com.android.tools.r8.partial.predicate.UnnamedPackageMatcher;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.base.Predicates;
 import com.google.common.base.Splitter;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.List;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 
 public class R8PartialCompilationConfiguration {
 
   private final boolean enabled;
-  private Path tempDir = null;
-  private final List<Predicate<DexString>> includePredicates;
-  private final List<Predicate<DexString>> excludePredicates;
+  private final R8PartialPredicateCollection includePredicates;
+  private final R8PartialPredicateCollection excludePredicates;
 
   public Consumer<InternalOptions> d8DexOptionsConsumer = ConsumerUtils.emptyConsumer();
   public Consumer<InternalOptions> r8OptionsConsumer = ConsumerUtils.emptyConsumer();
@@ -35,8 +34,8 @@
 
   private R8PartialCompilationConfiguration(
       boolean enabled,
-      List<Predicate<DexString>> includePredicates,
-      List<Predicate<DexString>> excludePredicates) {
+      R8PartialPredicateCollection includePredicates,
+      R8PartialPredicateCollection excludePredicates) {
     assert !enabled || !includePredicates.isEmpty();
     assert !enabled || excludePredicates != null;
     this.enabled = enabled;
@@ -44,19 +43,16 @@
     this.excludePredicates = excludePredicates;
   }
 
+  public R8PartialPredicateCollection getIncludePredicates() {
+    return includePredicates;
+  }
+
+  public R8PartialPredicateCollection getExcludePredicates() {
+    return excludePredicates;
+  }
+
   public boolean test(DexProgramClass clazz) {
-    DexString name = clazz.getType().getDescriptor();
-    for (Predicate<DexString> isR8ClassPredicate : includePredicates) {
-      if (isR8ClassPredicate.test(name)) {
-        for (Predicate<DexString> isD8ClassPredicate : excludePredicates) {
-          if (isD8ClassPredicate.test(name)) {
-            return false;
-          }
-        }
-        return true;
-      }
-    }
-    return false;
+    return includePredicates.test(clazz) && !excludePredicates.test(clazz);
   }
 
   public static R8PartialCompilationConfiguration disabledConfiguration() {
@@ -87,20 +83,11 @@
     return enabled;
   }
 
-  public synchronized Path getTempDir() throws IOException {
-    if (tempDir == null) {
-      setTempDir(Files.createTempDirectory("r8PartialCompilation"));
-    }
-    return tempDir;
-  }
-
-  public void setTempDir(Path tempDir) {
-    this.tempDir = tempDir;
-  }
-
   public static class Builder {
-    private final List<Predicate<DexString>> includePredicates = new ArrayList<>();
-    private final List<Predicate<DexString>> excludePredicates = new ArrayList<>();
+    private final R8PartialPredicateCollection includePredicates =
+        new R8PartialPredicateCollection();
+    private final R8PartialPredicateCollection excludePredicates =
+        new R8PartialPredicateCollection();
 
     private Builder() {}
 
@@ -110,70 +97,26 @@
     }
 
     public Builder includeAll() {
-      includePredicates.add(Predicates.alwaysTrue());
+      includePredicates.add(new AllClassesMatcher());
       return this;
     }
 
     public Builder addJavaTypeIncludePattern(String pattern) {
       includePredicates.add(
-          createMatcher("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
+          createPredicate("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
       return this;
     }
 
     public Builder addJavaTypeExcludePattern(String pattern) {
       excludePredicates.add(
-          createMatcher("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
+          createPredicate("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
       return this;
     }
 
-    public Builder addDescriptorIncludePattern(String pattern) {
-      includePredicates.add(createMatcher(pattern));
-      return this;
-    }
-
-    public Builder addDescriptorExcludePattern(String pattern) {
-      excludePredicates.add(createMatcher(pattern));
-      return this;
-    }
-
-    private Predicate<DexString> createMatcher(String descriptorPrefix) {
-      assert descriptorPrefix.startsWith("L");
-      assert descriptorPrefix.indexOf('.') == -1;
-
-      if (descriptorPrefix.equals("L**")) {
-        return new AllClassesMatcher();
-      } else if (descriptorPrefix.equals("L*")) {
-        return new UnnamedPackageMatcher();
-      } else if (descriptorPrefix.endsWith("/**")) {
-        return new PackageAndSubpackagePrefixMatcher(
-            descriptorPrefix.substring(0, descriptorPrefix.length() - 2));
-      } else if (descriptorPrefix.endsWith("/*")) {
-        return new PackagePrefixMatcher(
-            descriptorPrefix.substring(0, descriptorPrefix.length() - 1));
-      }
-      if (descriptorPrefix.endsWith("*")) {
-        return new ClassPrefixMatcher(descriptorPrefix.substring(0, descriptorPrefix.length() - 1));
-      } else {
-        return new ClassNameMatcher(descriptorPrefix + ';');
-      }
-    }
-
-    public Builder includeClasses(Class<?>... classes) {
-      return includeClasses(Arrays.asList(classes));
-    }
-
     public Builder includeClasses(Collection<Class<?>> classes) {
-      classes.forEach(
-          clazz ->
-              includePredicates.add(
-                  descriptor ->
-                      descriptor.toString().equals(DescriptorUtils.javaClassToDescriptor(clazz))));
-      return this;
-    }
-
-    public Builder includeJavaType(Predicate<String> include) {
-      includePredicates.add(
-          descriptor -> include.test(DescriptorUtils.descriptorToJavaType(descriptor.toString())));
+      for (Class<?> clazz : classes) {
+        includePredicates.add(new ClassNameMatcher(DescriptorUtils.javaClassToDescriptor(clazz)));
+      }
       return this;
     }
 
@@ -182,100 +125,30 @@
     }
 
     public Builder excludeClasses(Collection<Class<?>> classes) {
-      classes.forEach(
-          clazz ->
-              excludePredicates.add(
-                  descriptor ->
-                      descriptor.toString().equals(DescriptorUtils.javaClassToDescriptor(clazz))));
+      for (Class<?> clazz : classes) {
+        excludePredicates.add(new ClassNameMatcher(DescriptorUtils.javaClassToDescriptor(clazz)));
+      }
       return this;
     }
 
-    public Builder excludeJavaType(Predicate<String> exclude) {
-      excludePredicates.add(
-          descriptor -> exclude.test(DescriptorUtils.descriptorToJavaType(descriptor.toString())));
-      return this;
-    }
-  }
-
-  private static class AllClassesMatcher implements Predicate<DexString> {
-
-    AllClassesMatcher() {}
-
-    @Override
-    public boolean test(DexString descriptor) {
-      return true;
-    }
-  }
-
-  private static class UnnamedPackageMatcher implements Predicate<DexString> {
-
-    UnnamedPackageMatcher() {}
-
-    @Override
-    public boolean test(DexString descriptor) {
-      return descriptor.indexOf('/') == -1;
-    }
-  }
-
-  private static class PackageAndSubpackagePrefixMatcher implements Predicate<DexString> {
-
-    private final byte[] descriptorPrefix;
-
-    PackageAndSubpackagePrefixMatcher(String descriptorPrefix) {
-      this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
-    }
-
-    @Override
-    public boolean test(DexString descriptor) {
-      return descriptor.startsWith(descriptorPrefix);
-    }
-  }
-
-  private static class PackagePrefixMatcher implements Predicate<DexString> {
-
-    private final byte[] descriptorPrefix;
-    private final int descriptorPrefixLength;
-
-    PackagePrefixMatcher(String descriptorPrefix) {
-      this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
-      this.descriptorPrefixLength = descriptorPrefix.length();
-    }
-
-    @Override
-    public boolean test(DexString descriptor) {
-      return descriptor.startsWith(descriptorPrefix)
-          && descriptor.lastIndexOf('/') == descriptorPrefixLength - 1;
-    }
-  }
-
-  private static class ClassPrefixMatcher implements Predicate<DexString> {
-
-    private final byte[] descriptorPrefix;
-    private final int descriptorPrefixLength;
-
-    ClassPrefixMatcher(String descriptorPrefix) {
-      this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
-      this.descriptorPrefixLength = descriptorPrefix.length();
-    }
-
-    @Override
-    public boolean test(DexString descriptor) {
-      return descriptor.startsWith(descriptorPrefix)
-          && descriptor.lastIndexOf('/') < descriptorPrefixLength - 1;
-    }
-  }
-
-  private static class ClassNameMatcher implements Predicate<DexString> {
-
-    private final String descriptor;
-
-    ClassNameMatcher(String descriptor) {
-      this.descriptor = descriptor;
-    }
-
-    @Override
-    public boolean test(DexString descriptor) {
-      return descriptor.toString().equals(this.descriptor);
+    private R8PartialPredicate createPredicate(String descriptorPrefix) {
+      assert descriptorPrefix.startsWith("L");
+      assert descriptorPrefix.indexOf('.') == -1;
+      if (descriptorPrefix.equals(AllClassesMatcher.PATTERN)) {
+        return new AllClassesMatcher();
+      } else if (descriptorPrefix.equals(UnnamedPackageMatcher.PATTERN)) {
+        return new UnnamedPackageMatcher();
+      } else if (descriptorPrefix.endsWith("/**")) {
+        return new PackageAndSubpackagePrefixMatcher(
+            descriptorPrefix.substring(0, descriptorPrefix.length() - 2));
+      } else if (descriptorPrefix.endsWith("/*")) {
+        return new PackagePrefixMatcher(
+            descriptorPrefix.substring(0, descriptorPrefix.length() - 1));
+      } else if (descriptorPrefix.endsWith("*")) {
+        return new ClassPrefixMatcher(descriptorPrefix.substring(0, descriptorPrefix.length() - 1));
+      } else {
+        return new ClassNameMatcher(descriptorPrefix + ';');
+      }
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java b/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java
index 54ee91b..c4994e7 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialD8Result.java
@@ -5,21 +5,33 @@
 
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.profile.art.ArtProfileCollection;
+import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import java.util.Collection;
 
 public class R8PartialD8Result {
 
+  private final ArtProfileCollection artProfiles;
   private final ClassToFeatureSplitMap classToFeatureSplitMap;
   private final Collection<DexProgramClass> dexedClasses;
   private final Collection<DexProgramClass> desugaredClasses;
+  private final StartupProfile startupProfile;
 
   public R8PartialD8Result(
+      ArtProfileCollection artProfiles,
       ClassToFeatureSplitMap classToFeatureSplitMap,
       Collection<DexProgramClass> dexedClasses,
-      Collection<DexProgramClass> desugaredClasses) {
+      Collection<DexProgramClass> desugaredClasses,
+      StartupProfile startupProfile) {
+    this.artProfiles = artProfiles;
     this.classToFeatureSplitMap = classToFeatureSplitMap;
     this.dexedClasses = dexedClasses;
     this.desugaredClasses = desugaredClasses;
+    this.startupProfile = startupProfile;
+  }
+
+  public ArtProfileCollection getArtProfiles() {
+    return artProfiles;
   }
 
   public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
@@ -33,4 +45,8 @@
   public Collection<DexProgramClass> getDesugaredClasses() {
     return desugaredClasses;
   }
+
+  public StartupProfile getStartupProfile() {
+    return startupProfile;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialInputToDumpFlags.java b/src/main/java/com/android/tools/r8/partial/R8PartialInputToDumpFlags.java
deleted file mode 100644
index ddfef8f..0000000
--- a/src/main/java/com/android/tools/r8/partial/R8PartialInputToDumpFlags.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2025, 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.partial;
-
-import com.android.tools.r8.dump.DumpOptions;
-import com.android.tools.r8.utils.DumpInputFlags;
-import java.nio.file.Path;
-
-public class R8PartialInputToDumpFlags extends DumpInputFlags {
-
-  private final Path dumpFile;
-
-  public R8PartialInputToDumpFlags(Path dumpFile) {
-    this.dumpFile = dumpFile;
-  }
-
-  @Override
-  public Path getDumpPath() {
-    return dumpFile;
-  }
-
-  @Override
-  public boolean shouldDump(DumpOptions options) {
-    return true;
-  }
-
-  @Override
-  public boolean shouldFailCompilation() {
-    return false;
-  }
-
-  @Override
-  public boolean shouldLogDumpInfoMessage() {
-    return false;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
index cce90ee..cc39bf2 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
@@ -8,18 +8,29 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.Target;
+import com.android.tools.r8.profile.art.ArtProfile;
+import com.android.tools.r8.profile.art.ArtProfileCollection;
+import com.android.tools.r8.profile.art.ArtProfileMethodRule;
+import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.synthesis.SyntheticItems;
-import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.Timing;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public abstract class R8PartialSubCompilationConfiguration {
@@ -52,9 +63,11 @@
     private final Set<DexType> d8Types;
     private final Set<DexType> r8Types;
 
+    private ArtProfileCollection artProfiles;
     private ClassToFeatureSplitMap classToFeatureSplitMap;
     private Collection<DexProgramClass> dexedOutputClasses;
     private Collection<DexProgramClass> desugaredOutputClasses;
+    private StartupProfile startupProfile;
 
     public R8PartialD8SubCompilationConfiguration(
         Set<DexType> d8Types, Set<DexType> r8Types, Timing timing) {
@@ -63,6 +76,11 @@
       this.r8Types = r8Types;
     }
 
+    public ArtProfileCollection getArtProfiles() {
+      assert artProfiles != null;
+      return artProfiles;
+    }
+
     public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
       return classToFeatureSplitMap;
     }
@@ -77,6 +95,11 @@
       return desugaredOutputClasses;
     }
 
+    public StartupProfile getStartupProfile() {
+      assert startupProfile != null;
+      return startupProfile;
+    }
+
     public MethodConversionOptions.Target getTargetFor(
         ProgramDefinition definition, AppView<?> appView) {
       DexType type = definition.getContextType();
@@ -111,6 +134,7 @@
     }
 
     public void writeApplication(AppView<AppInfo> appView) {
+      artProfiles = appView.getArtProfileCollection().transformForR8Partial(appView);
       classToFeatureSplitMap =
           appView.appInfo().getClassToFeatureSplitMap().commitSyntheticsForR8Partial(appView);
       dexedOutputClasses = new ArrayList<>();
@@ -122,22 +146,34 @@
           desugaredOutputClasses.add(clazz);
         }
       }
+      startupProfile = appView.getStartupProfile();
     }
   }
 
   public static class R8PartialR8SubCompilationConfiguration
       extends R8PartialSubCompilationConfiguration {
 
+    private ArtProfileCollection artProfiles;
     private ClassToFeatureSplitMap classToFeatureSplitMap;
-    private Collection<DexProgramClass> dexingOutputClasses;
+    private Map<DexType, DexProgramClass> dexingOutputClasses;
+    private StartupProfile startupProfile;
 
     public R8PartialR8SubCompilationConfiguration(
+        ArtProfileCollection artProfiles,
         ClassToFeatureSplitMap classToFeatureSplitMap,
         Collection<DexProgramClass> dexingOutputClasses,
+        StartupProfile startupProfile,
         Timing timing) {
       super(timing);
+      this.artProfiles = artProfiles;
       this.classToFeatureSplitMap = classToFeatureSplitMap;
-      this.dexingOutputClasses = dexingOutputClasses;
+      this.dexingOutputClasses =
+          MapUtils.transform(dexingOutputClasses, IdentityHashMap::new, DexClass::getType);
+      this.startupProfile = startupProfile;
+    }
+
+    public ArtProfileCollection getArtProfiles() {
+      return artProfiles;
     }
 
     public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
@@ -146,30 +182,63 @@
 
     public Collection<DexProgramClass> getDexingOutputClasses() {
       assert dexingOutputClasses != null;
-      return dexingOutputClasses;
+      return dexingOutputClasses.values();
+    }
+
+    public StartupProfile getStartupProfile() {
+      assert startupProfile != null;
+      return startupProfile;
+    }
+
+    public void amendCompleteArtProfile(ArtProfile.Builder artProfileBuilder) {
+      List<DexProgramClass> dexingOutputClassesSorted =
+          ListUtils.sort(dexingOutputClasses.values(), Comparator.comparing(DexClass::getType));
+      for (DexProgramClass clazz : dexingOutputClassesSorted) {
+        artProfileBuilder.addClassRule(clazz.getType());
+        clazz.forEachMethod(
+            method ->
+                artProfileBuilder.addMethodRule(
+                    ArtProfileMethodRule.builder()
+                        .setMethod(method.getReference())
+                        .acceptMethodRuleInfoBuilder(
+                            methodRuleInfoBuilder ->
+                                methodRuleInfoBuilder.setIsHot().setIsStartup().setIsPostStartup())
+                        .build()));
+      }
     }
 
     public void commitDexingOutputClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
-      Set<DexType> dexingOutputTypes =
-          SetUtils.mapIdentityHashSet(dexingOutputClasses, DexClass::getType);
       DirectMappedDexApplication newApp =
           appView
               .app()
               .asDirect()
               .builder()
-              .removeClasspathClasses(clazz -> dexingOutputTypes.contains(clazz.getType()))
-              .addProgramClasses(dexingOutputClasses)
+              .removeClasspathClasses(clazz -> dexingOutputClasses.containsKey(clazz.getType()))
+              .addProgramClasses(dexingOutputClasses.values())
               .build();
       appView.rebuildAppInfo(newApp);
       assert amendMissingClasses(appView);
-      dexingOutputClasses = null;
+    }
+
+    public boolean hasD8DefinitionFor(DexReference reference) {
+      if (reference.isDexType()) {
+        return dexingOutputClasses.containsKey(reference.asDexType());
+      } else {
+        DexMember<?, ?> member = reference.asDexMember();
+        DexProgramClass holder = dexingOutputClasses.get(member.getHolderType());
+        return member.isDefinedOnClass(holder);
+      }
+    }
+
+    public boolean isD8Definition(ProgramDefinition definition) {
+      return hasD8DefinitionFor(definition.getReference());
     }
 
     private boolean amendMissingClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
       if (appView.hasLiveness()) {
         MissingClasses.Builder missingClassesBuilder =
             appView.appInfo().getMissingClasses().builder();
-        for (DexProgramClass clazz : dexingOutputClasses) {
+        for (DexProgramClass clazz : dexingOutputClasses.values()) {
           clazz.forEachImmediateSuperClassMatching(
               appView.app(),
               (supertype, superclass) -> superclass == null,
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/AllClassesMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/AllClassesMatcher.java
new file mode 100644
index 0000000..4c794d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/AllClassesMatcher.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import com.android.tools.r8.graph.DexString;
+
+public class AllClassesMatcher implements R8PartialPredicate {
+
+  public static final String PATTERN = "L**";
+
+  @Override
+  public boolean test(DexString descriptor) {
+    return true;
+  }
+
+  @Override
+  public String serializeToString() {
+    return "**";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/ClassNameMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/ClassNameMatcher.java
new file mode 100644
index 0000000..76f8603
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/ClassNameMatcher.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+public class ClassNameMatcher implements R8PartialPredicate {
+
+  private final String descriptor;
+
+  public ClassNameMatcher(String descriptor) {
+    assert descriptor.charAt(0) == 'L';
+    assert descriptor.charAt(descriptor.length() - 1) == ';';
+    this.descriptor = descriptor;
+  }
+
+  @Override
+  public boolean test(DexString descriptor) {
+    return descriptor.toString().equals(this.descriptor);
+  }
+
+  @Override
+  public String serializeToString() {
+    return DescriptorUtils.descriptorToJavaType(descriptor);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/ClassPrefixMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/ClassPrefixMatcher.java
new file mode 100644
index 0000000..3390bf2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/ClassPrefixMatcher.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static com.android.tools.r8.partial.predicate.R8PartialPredicateUtils.descriptorPrefixPatternToJavaTypePattern;
+
+import com.android.tools.r8.graph.DexString;
+import java.io.UTFDataFormatException;
+
+public class ClassPrefixMatcher implements R8PartialPredicate {
+
+  private final byte[] descriptorPrefix;
+  private final int descriptorPrefixLength;
+
+  public ClassPrefixMatcher(String descriptorPrefix) {
+    assert descriptorPrefix.charAt(0) == 'L';
+    assert descriptorPrefix.charAt(descriptorPrefix.length() - 1) != '/';
+    this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
+    this.descriptorPrefixLength = descriptorPrefix.length();
+  }
+
+  @Override
+  public boolean test(DexString descriptor) {
+    return descriptor.startsWith(descriptorPrefix)
+        && descriptor.lastIndexOf('/') < descriptorPrefixLength - 1;
+  }
+
+  @Override
+  public String serializeToString() {
+    try {
+      return descriptorPrefixPatternToJavaTypePattern(
+          DexString.decodeFromMutf8(descriptorPrefix, descriptorPrefixLength) + "*");
+    } catch (UTFDataFormatException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/PackageAndSubpackagePrefixMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/PackageAndSubpackagePrefixMatcher.java
new file mode 100644
index 0000000..b790ccb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/PackageAndSubpackagePrefixMatcher.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static com.android.tools.r8.partial.predicate.R8PartialPredicateUtils.descriptorPrefixPatternToJavaTypePattern;
+
+import com.android.tools.r8.graph.DexString;
+import java.io.UTFDataFormatException;
+
+public class PackageAndSubpackagePrefixMatcher implements R8PartialPredicate {
+
+  private final byte[] descriptorPrefix;
+  private final int descriptorPrefixLength;
+
+  public PackageAndSubpackagePrefixMatcher(String descriptorPrefix) {
+    assert descriptorPrefix.charAt(0) == 'L';
+    assert descriptorPrefix.charAt(descriptorPrefix.length() - 1) == '/';
+    this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
+    this.descriptorPrefixLength = descriptorPrefix.length();
+  }
+
+  @Override
+  public boolean test(DexString descriptor) {
+    return descriptor.startsWith(descriptorPrefix);
+  }
+
+  @Override
+  public String serializeToString() {
+    try {
+      return descriptorPrefixPatternToJavaTypePattern(
+          DexString.decodeFromMutf8(descriptorPrefix, descriptorPrefixLength) + "**");
+    } catch (UTFDataFormatException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/PackagePrefixMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/PackagePrefixMatcher.java
new file mode 100644
index 0000000..5307c22
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/PackagePrefixMatcher.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static com.android.tools.r8.partial.predicate.R8PartialPredicateUtils.descriptorPrefixPatternToJavaTypePattern;
+
+import com.android.tools.r8.graph.DexString;
+import java.io.UTFDataFormatException;
+
+public class PackagePrefixMatcher implements R8PartialPredicate {
+
+  private final byte[] descriptorPrefix;
+  private final int descriptorPrefixLength;
+
+  public PackagePrefixMatcher(String descriptorPrefix) {
+    assert descriptorPrefix.charAt(0) == 'L';
+    assert descriptorPrefix.charAt(descriptorPrefix.length() - 1) == '/';
+    this.descriptorPrefix = DexString.encodeToMutf8(descriptorPrefix);
+    this.descriptorPrefixLength = descriptorPrefix.length();
+  }
+
+  @Override
+  public boolean test(DexString descriptor) {
+    return descriptor.startsWith(descriptorPrefix)
+        && descriptor.lastIndexOf('/') == descriptorPrefixLength - 1;
+  }
+
+  @Override
+  public String serializeToString() {
+    try {
+      return descriptorPrefixPatternToJavaTypePattern(
+          DexString.decodeFromMutf8(descriptorPrefix, descriptorPrefixLength) + "*");
+    } catch (UTFDataFormatException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicate.java b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicate.java
new file mode 100644
index 0000000..b28ac73
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicate.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import com.android.tools.r8.graph.DexString;
+import java.util.function.Predicate;
+
+public interface R8PartialPredicate extends Predicate<DexString> {
+
+  String serializeToString();
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateCollection.java b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateCollection.java
new file mode 100644
index 0000000..2b8339b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateCollection.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import java.util.ArrayList;
+import java.util.List;
+
+public class R8PartialPredicateCollection {
+
+  private final List<R8PartialPredicate> predicates = new ArrayList<>();
+
+  public void add(R8PartialPredicate predicate) {
+    predicates.add(predicate);
+  }
+
+  public boolean isEmpty() {
+    return predicates.isEmpty();
+  }
+
+  public boolean test(DexProgramClass clazz) {
+    DexString descriptor = clazz.getType().getDescriptor();
+    for (R8PartialPredicate predicate : predicates) {
+      if (predicate.test(descriptor)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public byte[] getDumpFileContent() {
+    assert !isEmpty();
+    StringBuilder builder = new StringBuilder();
+    builder.append(predicates.iterator().next().serializeToString());
+    for (int i = 1; i < predicates.size(); i++) {
+      builder.append('\n').append(predicates.get(i).serializeToString());
+    }
+    return builder.toString().getBytes(UTF_8);
+  }
+
+  public String serializeToString() {
+    throw new Unimplemented();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateUtils.java b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateUtils.java
new file mode 100644
index 0000000..7b3d165
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/R8PartialPredicateUtils.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import static com.android.tools.r8.utils.DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR;
+import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
+
+public class R8PartialPredicateUtils {
+
+  static String descriptorPrefixPatternToJavaTypePattern(String descriptorPrefixPattern) {
+    assert descriptorPrefixPattern.charAt(0) == 'L';
+    assert descriptorPrefixPattern.charAt(descriptorPrefixPattern.length() - 1) != ';';
+    return descriptorPrefixPattern
+        .substring(1)
+        .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/partial/predicate/UnnamedPackageMatcher.java b/src/main/java/com/android/tools/r8/partial/predicate/UnnamedPackageMatcher.java
new file mode 100644
index 0000000..fd04ccb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/partial/predicate/UnnamedPackageMatcher.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2025, 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.partial.predicate;
+
+import com.android.tools.r8.graph.DexString;
+
+public class UnnamedPackageMatcher implements R8PartialPredicate {
+
+  public static final String PATTERN = "L*";
+
+  @Override
+  public boolean test(DexString descriptor) {
+    return descriptor.indexOf('/') == -1;
+  }
+
+  @Override
+  public String serializeToString() {
+    return "*";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
index 16058e4..df2c15b 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.TextOutputStream;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
@@ -17,6 +16,7 @@
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration;
 import com.android.tools.r8.profile.AbstractProfile;
 import com.android.tools.r8.profile.AbstractProfileRule;
 import com.android.tools.r8.utils.InternalOptions;
@@ -179,15 +179,13 @@
     AppInfo appInfo = appView.appInfo();
     return transform(
         (classRule, builder) -> {
-          if (appInfo.hasDefinitionForWithoutExistenceAssert(classRule.getType())) {
+          if (hasDefinitionFor(appInfo, classRule.getType())) {
             builder.addClassRule(
                 ArtProfileClassRule.builder().setType(classRule.getType()).build());
           }
         },
         (methodRule, builder) -> {
-          DexClass clazz =
-              appInfo.definitionForWithoutExistenceAssert(methodRule.getMethod().getHolderType());
-          if (methodRule.getMethod().isDefinedOnClass(clazz)) {
+          if (hasDefinitionFor(appInfo, methodRule.getMethod())) {
             builder.addMethodRule(
                 ArtProfileMethodRule.builder()
                     .setMethod(methodRule.getMethod())
@@ -199,6 +197,17 @@
         });
   }
 
+  private boolean hasDefinitionFor(AppInfo appInfo, DexReference reference) {
+    if (appInfo.hasDefinitionForWithoutExistenceAssert(reference)) {
+      return true;
+    }
+    R8PartialSubCompilationConfiguration subCompilationConfiguration =
+        appInfo.options().partialSubCompilationConfiguration;
+    return subCompilationConfiguration != null
+        && subCompilationConfiguration.isR8()
+        && subCompilationConfiguration.asR8().hasD8DefinitionFor(reference);
+  }
+
   public ArtProfile withoutPrunedItems(PrunedItems prunedItems) {
     rules.keySet().removeIf(prunedItems::isRemoved);
     return this;
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
index 6f4fb22..c371080 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
@@ -10,9 +10,10 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -22,16 +23,21 @@
   public static ArtProfileCollection createInitialArtProfileCollection(
       AppInfo appInfo, InternalOptions options) {
     ArtProfileOptions artProfileOptions = options.getArtProfileOptions();
-    Collection<ArtProfileProvider> artProfileProviders = artProfileOptions.getArtProfileProviders();
-    List<ArtProfile> artProfiles =
-        new ArrayList<>(
-            artProfileProviders.size()
-                + BooleanUtils.intValue(artProfileOptions.isCompletenessCheckForTestingEnabled()));
-    for (ArtProfileProvider artProfileProvider : artProfileProviders) {
-      ArtProfile.Builder artProfileBuilder =
-          ArtProfile.builderForInitialArtProfile(artProfileProvider, options);
-      artProfileProvider.getArtProfile(artProfileBuilder);
-      artProfiles.add(artProfileBuilder.build());
+    List<ArtProfile> artProfiles = new ArrayList<>();
+    if (options.partialSubCompilationConfiguration == null
+        || options.partialSubCompilationConfiguration.isD8()) {
+      Collection<ArtProfileProvider> artProfileProviders =
+          artProfileOptions.getArtProfileProviders();
+      for (ArtProfileProvider artProfileProvider : artProfileProviders) {
+        ArtProfile.Builder artProfileBuilder =
+            ArtProfile.builderForInitialArtProfile(artProfileProvider, options);
+        artProfileProvider.getArtProfile(artProfileBuilder);
+        artProfiles.add(artProfileBuilder.build());
+      }
+    } else {
+      assert options.partialSubCompilationConfiguration.isR8();
+      Iterables.addAll(
+          artProfiles, options.partialSubCompilationConfiguration.asR8().getArtProfiles());
     }
     if (artProfileOptions.isCompletenessCheckForTestingEnabled()) {
       artProfiles.add(createCompleteArtProfile(appInfo));
@@ -48,8 +54,7 @@
   private static ArtProfile createCompleteArtProfile(AppInfo appInfo) {
     ArtProfile.Builder artProfileBuilder = ArtProfile.builder();
     for (DexProgramClass clazz : appInfo.classesWithDeterministicOrder()) {
-      artProfileBuilder.addClassRule(
-          ArtProfileClassRule.builder().setType(clazz.getType()).build());
+      artProfileBuilder.addClassRule(clazz.getType());
       clazz.forEachMethod(
           method ->
               artProfileBuilder.addMethodRule(
@@ -60,6 +65,11 @@
                               methodRuleInfoBuilder.setIsHot().setIsStartup().setIsPostStartup())
                       .build()));
     }
+    R8PartialSubCompilationConfiguration subCompilationConfiguration =
+        appInfo.options().partialSubCompilationConfiguration;
+    if (subCompilationConfiguration != null && subCompilationConfiguration.isR8()) {
+      subCompilationConfiguration.asR8().amendCompleteArtProfile(artProfileBuilder);
+    }
     return artProfileBuilder.build();
   }
 
@@ -80,6 +90,8 @@
 
   public abstract void supplyConsumers(AppView<?> appView);
 
+  public abstract ArtProfileCollection transformForR8Partial(AppView<AppInfo> appView);
+
   public abstract ArtProfileCollection withoutMissingItems(AppView<?> appView);
 
   public abstract ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems, Timing timing);
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
index 45b3072..75d7cfe 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
@@ -37,6 +37,17 @@
     this.options = options;
   }
 
+  // Constructor for D8 in R8 partial.
+  public ArtProfileOptions(InternalOptions options, ArtProfileOptions artProfileOptions) {
+    this(options);
+    this.artProfilesForRewriting = artProfileOptions.artProfilesForRewriting;
+    this.enableCompletenessCheckForTesting = artProfileOptions.enableCompletenessCheckForTesting;
+    this.enableNopCheckForTesting = artProfileOptions.enableNopCheckForTesting;
+    this.hasReadArtProfileProviders = artProfileOptions.hasReadArtProfileProviders;
+    this.allowReadingEmptyArtProfileProvidersMultipleTimesForTesting =
+        artProfileOptions.allowReadingEmptyArtProfileProvidersMultipleTimesForTesting;
+  }
+
   public Collection<ArtProfileForRewriting> getArtProfilesForRewriting() {
     return artProfilesForRewriting;
   }
@@ -45,6 +56,8 @@
     assert !hasReadArtProfileProviders
         || (allowReadingEmptyArtProfileProvidersMultipleTimesForTesting
             && artProfilesForRewriting.isEmpty());
+    assert options.partialSubCompilationConfiguration == null
+        || options.partialSubCompilationConfiguration.isD8();
     hasReadArtProfileProviders = true;
     return ListUtils.map(artProfilesForRewriting, ArtProfileForRewriting::getArtProfileProvider);
   }
@@ -57,9 +70,7 @@
     return enableCompletenessCheckForTesting
         && !options.getLibraryDesugaringOptions().isDesugaredLibraryCompilation()
         && !options.getStartupOptions().isStartupCompletenessCheckForTestingEnabled()
-        && !options.getInstrumentationOptions().isInstrumentationEnabled()
-        // TODO(b/390355818): Enable completeness testing for R8 partial.
-        && options.partialSubCompilationConfiguration == null;
+        && !options.getInstrumentationOptions().isInstrumentationEnabled();
   }
 
   public boolean isNopCheckForTestingEnabled() {
diff --git a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
index 42ab16d..5b3f159 100644
--- a/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/EmptyArtProfileCollection.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.profile.art;
 
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -58,6 +59,11 @@
   }
 
   @Override
+  public ArtProfileCollection transformForR8Partial(AppView<AppInfo> appView) {
+    return this;
+  }
+
+  @Override
   public ArtProfileCollection withoutMissingItems(AppView<?> appView) {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
index 926bab4..4f98cdf 100644
--- a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.profile.art;
 
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -96,6 +97,28 @@
     }
   }
 
+  /**
+   * Processes the D8 art profiles before forwarding them to R8, in R8 partial.
+   *
+   * <p>When testing, a synthetic art profile is injected for completeness testing. This first runs
+   * the completeness checker and then removes the synthetic profile.
+   *
+   * <p>In release, this returns the art profiles as-is.
+   */
+  @Override
+  public ArtProfileCollection transformForR8Partial(AppView<AppInfo> appView) {
+    ArtProfileOptions artProfileOptions = appView.options().getArtProfileOptions();
+    assert !artProfileOptions.isNopCheckForTestingEnabled();
+    if (artProfileOptions.isCompletenessCheckForTestingEnabled()) {
+      assert ArtProfileCompletenessChecker.verify(appView);
+      ListUtils.removeLast(artProfiles);
+      if (artProfiles.isEmpty()) {
+        return ArtProfileCollection.empty();
+      }
+    }
+    return this;
+  }
+
   @Override
   public ArtProfileCollection withoutMissingItems(AppView<?> appView) {
     return map(artProfile -> artProfile.withoutMissingItems(appView));
diff --git a/src/main/java/com/android/tools/r8/profile/art/ThrowingArtProfileConsumer.java b/src/main/java/com/android/tools/r8/profile/art/ThrowingArtProfileConsumer.java
new file mode 100644
index 0000000..e2d61e0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ThrowingArtProfileConsumer.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2025, 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.profile.art;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TextOutputStream;
+import com.android.tools.r8.errors.Unreachable;
+
+public class ThrowingArtProfileConsumer implements ArtProfileConsumer {
+
+  @Override
+  public TextOutputStream getHumanReadableArtProfileConsumer() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public ArtProfileRuleConsumer getRuleConsumer() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void finished(DiagnosticsHandler handler) {
+    throw new Unreachable();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
index 7fa618c..2882170 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
@@ -80,6 +80,12 @@
         context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
   }
 
+  public void addMethodAndHolderIfContextIsInProfile(
+      ProgramMethod method, ProgramDefinition context) {
+    applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+  }
+
   void applyIfContextIsInProfile(
       ProgramDefinition context, Consumer<ProfileAdditionsBuilder> builderConsumer) {
     if (context.isProgramClass()) {
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
index 3af5dc4..8a65064 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
@@ -342,6 +342,12 @@
   }
 
   @Override
+  public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+    parent.acceptAutoCloseableDispatchMethod(method, context);
+  }
+
+  @Override
   public void acceptRecordClass(DexProgramClass recordClass) {
     parent.acceptRecordClass(recordClass);
   }
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java
index 944f860..926c9d7 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
@@ -106,11 +107,28 @@
   }
 
   @Override
+  public void acceptAutoCloseableForwardingMethod(ProgramMethod method, ProgramDefinition context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+    parent.acceptAutoCloseableForwardingMethod(method, context);
+  }
+
+  @Override
+  public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+    parent.acceptAutoCloseableDispatchMethod(method, context);
+  }
+
+  @Override
   public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
     parent.acceptInterfaceInjection(clazz, newInterface);
   }
 
   @Override
+  public void acceptAutoCloseableInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
+    parent.acceptAutoCloseableInterfaceInjection(clazz, newInterface);
+  }
+
+  @Override
   public void acceptInterfaceMethodDesugaringForwardingMethod(
       ProgramMethod method, DexClassAndMethod baseMethod) {
     additionsCollection.addMethodIfContextIsInProfile(method, baseMethod);
diff --git a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
index 62ee585..61c845e 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault;
 
 import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SystemPropertyUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
@@ -65,7 +66,10 @@
 
   private Collection<StartupProfileProvider> startupProfileProviders;
 
-  public StartupOptions() {
+  private final InternalOptions options;
+
+  public StartupOptions(InternalOptions options) {
+    this.options = options;
     this.startupProfileProviders =
         SystemPropertyUtils.applySystemProperty(
             "com.android.tools.r8.startup.profile",
@@ -76,6 +80,21 @@
             Collections::emptyList);
   }
 
+  public StartupOptions(InternalOptions options, StartupOptions startupOptions) {
+    this(options);
+    this.enableOutlining = startupOptions.enableOutlining;
+    this.enableMinimalStartupDex = startupOptions.enableMinimalStartupDex;
+    this.enableStartupBoundaryOptimizations = startupOptions.enableStartupBoundaryOptimizations;
+    this.enableStartupCompletenessCheckForTesting =
+        startupOptions.enableStartupCompletenessCheckForTesting;
+    this.enableStartupLayoutOptimization = startupOptions.enableStartupLayoutOptimization;
+    this.enableStartupMixedSectionLayoutOptimizations =
+        startupOptions.enableStartupMixedSectionLayoutOptimizations;
+    this.multiStartupDexDistributionStrategyName =
+        startupOptions.multiStartupDexDistributionStrategyName;
+    this.startupProfileProviders = startupOptions.startupProfileProviders;
+  }
+
   public boolean isOutliningEnabled() {
     return enableOutlining;
   }
@@ -141,6 +160,8 @@
   }
 
   public Collection<StartupProfileProvider> getStartupProfileProviders() {
+    assert options.partialSubCompilationConfiguration == null
+        || options.partialSubCompilationConfiguration.isD8();
     return startupProfileProviders;
   }
 
diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java
index f675a24..609e300 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/profile/NonEmptyStartupProfile.java
@@ -6,12 +6,12 @@
 
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.partial.R8PartialSubCompilationConfiguration;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.SetUtils;
@@ -129,20 +129,28 @@
     AppInfo appInfo = appView.appInfo();
     return transform(
         (classRule, builder) -> {
-          if (appInfo.hasDefinitionForWithoutExistenceAssert(classRule.getReference())) {
+          if (hasDefinitionFor(appInfo, classRule.getReference())) {
             builder.addClassRule(classRule);
           }
         },
         (methodRule, builder) -> {
-          DexClass clazz =
-              appInfo.definitionForWithoutExistenceAssert(
-                  methodRule.getReference().getHolderType());
-          if (methodRule.getReference().isDefinedOnClass(clazz)) {
+          if (hasDefinitionFor(appInfo, methodRule.getReference())) {
             builder.addMethodRule(methodRule);
           }
         });
   }
 
+  private boolean hasDefinitionFor(AppInfo appInfo, DexReference reference) {
+    if (appInfo.hasDefinitionForWithoutExistenceAssert(reference)) {
+      return true;
+    }
+    R8PartialSubCompilationConfiguration subCompilationConfiguration =
+        appInfo.options().partialSubCompilationConfiguration;
+    return subCompilationConfiguration != null
+        && subCompilationConfiguration.isR8()
+        && subCompilationConfiguration.asR8().hasD8DefinitionFor(reference);
+  }
+
   @Override
   public StartupProfile withoutPrunedItems(
       PrunedItems prunedItems, SyntheticItems syntheticItems, Timing timing) {
diff --git a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java
index e6a4cb5..2b08062 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/profile/StartupProfile.java
@@ -65,18 +65,23 @@
     return startupProfile != null ? startupProfile : empty();
   }
 
-  public static StartupProfile createInitialStartupProfileForD8(AppView<?> appView) {
+  public static StartupProfile createInitialStartupProfileForD8(DexApplication application) {
     return createInitialStartupProfile(
-        appView.options(),
-        origin -> new MissingStartupProfileItemsDiagnostic.Builder(appView).setOrigin(origin));
+        application.options,
+        origin -> new MissingStartupProfileItemsDiagnostic.Builder(application).setOrigin(origin));
   }
 
   public static StartupProfile createInitialStartupProfileForR8(DexApplication application) {
     // In R8 we expect a startup profile that matches the input app. Since profiles gathered from
     // running on ART will include synthetics, and these synthetics are not in the input app, we do
     // not raise warnings if some rules in the profile do not match anything.
-    return createInitialStartupProfile(
-        application.options, origin -> MissingStartupProfileItemsDiagnostic.Builder.nop());
+    InternalOptions options = application.options;
+    if (options.partialSubCompilationConfiguration != null) {
+      return options.partialSubCompilationConfiguration.asR8().getStartupProfile();
+    } else {
+      return createInitialStartupProfile(
+          options, origin -> MissingStartupProfileItemsDiagnostic.Builder.nop());
+    }
   }
 
   public static StartupProfile empty() {
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java b/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java
index 5925d3f..fe09d4b 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java
@@ -203,5 +203,10 @@
     public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
       return true;
     }
+
+    @Override
+    public NamingLens withoutDesugaredLibraryPrefixRewritingNamingLens() {
+      return this;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 058e951..6867016 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.shaking.ifrules.MaterializedSubsequentRulesOptimizer;
@@ -23,7 +22,7 @@
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.UncheckedExecutionException;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
@@ -41,21 +40,18 @@
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final DexItemFactory factory;
-  private final SubtypingInfo subtypingInfo;
   private final Enqueuer enqueuer;
   private final ConsequentRootSetBuilder rootSetBuilder;
   private final TaskCollection<?> tasks;
 
   IfRuleEvaluator(
       AppView<? extends AppInfoWithClassHierarchy> appView,
-      SubtypingInfo subtypingInfo,
       Enqueuer enqueuer,
       ConsequentRootSetBuilder rootSetBuilder,
       TaskCollection<?> tasks) {
     assert tasks.isEmpty();
     this.appView = appView;
     this.factory = appView.dexItemFactory();
-    this.subtypingInfo = subtypingInfo;
     this.enqueuer = enqueuer;
     this.rootSetBuilder = rootSetBuilder;
     this.tasks = tasks;
@@ -101,11 +97,10 @@
     if (classKind == ClassKind.PROGRAM) {
       return ifRule.relevantCandidatesForRule(
           appView,
-          subtypingInfo,
+          enqueuer.getSubtypingInfo(),
           (Iterable<DexProgramClass>) classesWithNewlyLiveMembers,
           isEffectivelyLive);
     }
-    assert classKind == ClassKind.LIBRARY;
     return classesWithNewlyLiveMembers;
   }
 
@@ -361,7 +356,7 @@
               .collect(Collectors.toList());
       // Member rules are combined as AND logic: if found unsatisfied member rule, this
       // combination of live members is not a good fit.
-      ProgramMethodSet methodsSatisfyingRule = ProgramMethodSet.create();
+      DexClassAndMethodSet methodsSatisfyingRule = DexClassAndMethodSet.create();
       boolean satisfied =
           memberKeepRules.stream()
               .allMatch(
@@ -372,7 +367,7 @@
                     DexClassAndMethod methodSatisfyingRule =
                         rootSetBuilder.getMethodSatisfyingRule(memberRule, methodsInCombination);
                     if (methodSatisfyingRule != null) {
-                      methodsSatisfyingRule.add(methodSatisfyingRule.asProgramMethod());
+                      methodsSatisfyingRule.add(methodSatisfyingRule);
                       return true;
                     }
                     return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
index 6075570..fa32a02 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
 import com.android.tools.r8.graph.analysis.FixpointEnqueuerAnalysis;
 import com.android.tools.r8.graph.analysis.NewlyLiveClassEnqueuerAnalysis;
@@ -33,6 +32,7 @@
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
@@ -145,26 +145,50 @@
     return false;
   }
 
-  public ConsequentRootSet applyActiveIfRulesToLibraryClasses(Enqueuer enqueuer, Timing timing)
+  public void applyActiveIfRulesToClasspathClasses(
+      ConsequentRootSetBuilder consequentRootSetBuilder, Enqueuer enqueuer, Timing timing)
       throws ExecutionException {
-    timing.begin("Apply if rules to library classes");
-    SubtypingInfo subtypingInfo = enqueuer.getSubtypingInfo();
-    ConsequentRootSetBuilder consequentRootSetBuilder =
-        ConsequentRootSet.builder(appView, enqueuer, subtypingInfo);
+    try (Timing t = timing.begin("Apply if rules to classpath classes")) {
+      applyActiveIfRulesToNonProgramClass(
+          consequentRootSetBuilder,
+          enqueuer,
+          appView.app().asDirect().classpathClasses(),
+          ClassKind.CLASSPATH,
+          timing);
+    }
+  }
+
+  public void applyActiveIfRulesToLibraryClasses(
+      ConsequentRootSetBuilder consequentRootSetBuilder, Enqueuer enqueuer, Timing timing)
+      throws ExecutionException {
+    if (!appView.testing().applyIfRulesToLibrary) {
+      return;
+    }
+    try (Timing t = timing.begin("Apply if rules to library classes")) {
+      applyActiveIfRulesToNonProgramClass(
+          consequentRootSetBuilder,
+          enqueuer,
+          appView.app().asDirect().libraryClasses(),
+          ClassKind.LIBRARY,
+          timing);
+    }
+  }
+
+  private <T extends DexClass> void applyActiveIfRulesToNonProgramClass(
+      ConsequentRootSetBuilder consequentRootSetBuilder,
+      Enqueuer enqueuer,
+      Collection<T> classes,
+      ClassKind<T> classKind,
+      Timing timing)
+      throws ExecutionException {
     IfRuleEvaluator evaluator =
-        new IfRuleEvaluator(appView, subtypingInfo, enqueuer, consequentRootSetBuilder, tasks);
+        new IfRuleEvaluator(appView, enqueuer, consequentRootSetBuilder, tasks);
     evaluator.processActiveIfRulesWithMembers(
-        activeIfRulesWithMembers,
-        ClassKind.LIBRARY,
-        appView.app().asDirect().libraryClasses(),
-        alwaysTrue());
+        activeIfRulesWithMembers, classKind, classes, alwaysTrue());
     evaluator.processActiveIfRulesWithoutMembers(
         activeIfRulesWithoutMembers,
-        newIdentityHashMapFromCollection(
-            appView.app().asDirect().libraryClasses(), DexClass::getType, Function.identity()),
+        newIdentityHashMapFromCollection(classes, DexClass::getType, Function.identity()),
         timing);
-    timing.end();
-    return consequentRootSetBuilder.buildConsequentRootSet();
   }
 
   @Override
@@ -172,9 +196,12 @@
       Enqueuer enqueuer, EnqueuerWorklist worklist, ExecutorService executorService, Timing timing)
       throws ExecutionException {
     boolean isFirstFixpoint = setSeenFixpoint();
-    if (isFirstFixpoint && appView.testing().applyIfRulesToLibrary) {
-      ConsequentRootSet consequentRootSet = applyActiveIfRulesToLibraryClasses(enqueuer, timing);
-      enqueuer.addConsequentRootSet(consequentRootSet);
+    if (isFirstFixpoint) {
+      ConsequentRootSetBuilder consequentRootSetBuilder =
+          ConsequentRootSet.builder(appView, enqueuer);
+      applyActiveIfRulesToClasspathClasses(consequentRootSetBuilder, enqueuer, timing);
+      applyActiveIfRulesToLibraryClasses(consequentRootSetBuilder, enqueuer, timing);
+      enqueuer.addConsequentRootSet(consequentRootSetBuilder.buildConsequentRootSet());
     }
     if (!shouldProcessActiveIfRulesWithMembers(isFirstFixpoint)
         && !shouldProcessActiveIfRulesWithoutMembers(isFirstFixpoint)) {
@@ -201,11 +228,10 @@
 
   private ConsequentRootSet processActiveIfRules(
       Enqueuer enqueuer, boolean isFirstFixpoint, Timing timing) throws ExecutionException {
-    SubtypingInfo subtypingInfo = enqueuer.getSubtypingInfo();
     ConsequentRootSetBuilder consequentRootSetBuilder =
-        ConsequentRootSet.builder(appView, enqueuer, subtypingInfo);
+        ConsequentRootSet.builder(appView, enqueuer);
     IfRuleEvaluator evaluator =
-        new IfRuleEvaluator(appView, subtypingInfo, enqueuer, consequentRootSetBuilder, tasks);
+        new IfRuleEvaluator(appView, enqueuer, consequentRootSetBuilder, tasks);
     timing.begin("If rules with members");
     if (shouldProcessActiveIfRulesWithMembers(isFirstFixpoint)) {
       processActiveIfRulesWithMembers(evaluator, isFirstFixpoint);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
index cb97db1..81919d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
@@ -3,23 +3,24 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.Definition;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 
 public class ProguardIfRulePreconditionMatch {
 
   private final ProguardIfRule ifRule;
   private final DexClass classMatch;
-  private final ProgramMethodSet methodsMatch;
+  private final DexClassAndMethodSet methodsMatch;
 
   public ProguardIfRulePreconditionMatch(ProguardIfRule ifRule, DexClass classMatch) {
-    this(ifRule, classMatch, ProgramMethodSet.empty());
+    this(ifRule, classMatch, DexClassAndMethodSet.empty());
   }
 
   public ProguardIfRulePreconditionMatch(
-      ProguardIfRule ifRule, DexClass classMatch, ProgramMethodSet methodsMatch) {
+      ProguardIfRule ifRule, DexClass classMatch, DexClassAndMethodSet methodsMatch) {
     this.ifRule = ifRule;
     this.classMatch = classMatch;
     this.methodsMatch = methodsMatch;
@@ -50,13 +51,18 @@
   }
 
   private void disallowMethodOptimizationsForReevaluation(ConsequentRootSetBuilder rootSetBuilder) {
-    for (ProgramMethod method : methodsMatch) {
-      rootSetBuilder
-          .getDependentMinimumKeepInfo()
-          .getOrCreateUnconditionalMinimumKeepInfoFor(method.getReference())
-          .asMethodJoiner()
-          .disallowClassInlining()
-          .disallowInlining();
+    if (classMatch.isProgramClass()) {
+      for (DexClassAndMethod method : methodsMatch) {
+        assert method.isProgramMethod();
+        rootSetBuilder
+            .getDependentMinimumKeepInfo()
+            .getOrCreateUnconditionalMinimumKeepInfoFor(method.getReference())
+            .asMethodJoiner()
+            .disallowClassInlining()
+            .disallowInlining();
+      }
+    } else {
+      assert methodsMatch.stream().noneMatch(Definition::isProgramMethod);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index c1befbf..65e7088 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -231,8 +231,14 @@
       R8PartialUseCollector useCollector =
           new R8PartialUseCollector(appView) {
 
+            // Allow D8/R8 boundary obfuscation. We disable repackaging of the R8 part since
+            // repackaging uses a graph lens, which would need to be applied to the D8 part before
+            // the application writer (though this is perfectly doable).
             private final ProguardKeepRuleModifiers modifiers =
-                ProguardKeepRuleModifiers.builder().build();
+                ProguardKeepRuleModifiers.builder()
+                    .setAllowsObfuscation(true)
+                    .setAllowsRepackaging(false)
+                    .build();
             // TODO(b/390576160): Add a test that this works when using -whyareyoukeeping.
             private final ReferencedFromD8InR8PartialFakeProguardRule keepRule =
                 new ReferencedFromD8InR8PartialFakeProguardRule();
@@ -2394,10 +2400,8 @@
     }
 
     static ConsequentRootSetBuilder builder(
-        AppView<? extends AppInfoWithClassHierarchy> appView,
-        Enqueuer enqueuer,
-        SubtypingInfo subtypingInfo) {
-      return new ConsequentRootSetBuilder(appView, enqueuer, subtypingInfo);
+        AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+      return new ConsequentRootSetBuilder(appView, enqueuer, enqueuer.getSubtypingInfo());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 0030cec..1ef8edc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -66,6 +66,10 @@
   public final SyntheticKind CONST_DYNAMIC = generator.forNonSharableInstanceClass("$Condy");
 
   // Method synthetics.
+  public final SyntheticKind AUTOCLOSEABLE_DISPATCHER =
+      generator.forSingleMethodWithGlobalMerging("AutoCloseableDispatcher");
+  public final SyntheticKind AUTOCLOSEABLE_FORWARDER =
+      generator.forSingleMethodWithGlobalMerging("AutoCloseableForwarder");
   public final SyntheticKind TYPE_SWITCH_HELPER =
       generator.forSingleMethodWithGlobalMerging("TypeSwitch");
   public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD =
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 f3c1487..3a83d41 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -43,6 +43,7 @@
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.partial.R8PartialCompilationConfiguration;
 import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
 import com.android.tools.r8.profile.startup.StartupProfileProviderUtils;
@@ -106,6 +107,8 @@
   private static final String dumpLibraryFileName = "library.jar";
   private static final String dumpConfigFileName = "proguard.config";
   private static final String dumpInputConfigFileName = "proguard_input.config";
+  public static final String dumpR8IncludeFileName = "r8-include.txt";
+  public static final String dumpR8ExcludeFileName = "r8-exclude.txt";
 
   private static Map<FeatureSplit, String> dumpFeatureSplitFileNames(
       FeatureSplitConfiguration featureSplitConfiguration, String postfix) {
@@ -496,8 +499,8 @@
     return programResourcesMainDescriptor.get(resource);
   }
 
+  @SuppressWarnings("UnusedVariable")
   public void dump(Path output, DumpOptions dumpOptions, InternalOptions options) {
-    int nextDexIndex = 0;
     OpenOption[] openOptions =
         new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
     try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(output, openOptions))) {
@@ -580,6 +583,10 @@
           }
         }
       }
+      if (dumpOptions.hasPartialCompilationConfiguration()) {
+        dumpPartialCompilationConfiguration(dumpOptions.getPartialCompilationConfiguration(), out);
+      }
+      int nextDexIndex = 0;
       nextDexIndex =
           dumpProgramResources(
               dumpProgramFileName,
@@ -677,6 +684,23 @@
     }
   }
 
+  private void dumpPartialCompilationConfiguration(
+      R8PartialCompilationConfiguration partialCompilationConfiguration, ZipOutputStream out)
+      throws IOException {
+    writeToZipStream(
+        out,
+        dumpR8IncludeFileName,
+        partialCompilationConfiguration.getIncludePredicates().getDumpFileContent(),
+        ZipEntry.DEFLATED);
+    if (!partialCompilationConfiguration.getExcludePredicates().isEmpty()) {
+      writeToZipStream(
+          out,
+          dumpR8ExcludeFileName,
+          partialCompilationConfiguration.getExcludePredicates().getDumpFileContent(),
+          ZipEntry.DEFLATED);
+    }
+  }
+
   private int dumpProgramResources(
       String archiveName,
       FeatureSplitConfiguration featureSplitConfiguration,
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index ab542a4..f0f345b 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -13,9 +13,12 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.utils.compiledump.CompilerCommandDumpUtils;
+import com.android.tools.r8.utils.compiledump.R8PartialDumpUtils;
 import com.android.tools.r8.utils.compiledump.ResourceShrinkerDumpUtils;
 import com.android.tools.r8.utils.compiledump.StartupProfileDumpUtils;
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -65,6 +68,8 @@
           "--pg-map-output",
           "--desugared-lib",
           "--desugared-lib-pg-conf-output",
+          "--partial-include",
+          "--partial-exclude",
           "--threads",
           "--startup-profile");
 
@@ -99,12 +104,14 @@
   }
 
   @SuppressWarnings({"StringSplitter", "BadImport"})
-  public static void main(String[] args) throws CompilationFailedException {
+  public static void main(String[] args) throws CompilationFailedException, IOException {
     boolean isCompatMode = false;
     OutputMode outputMode = OutputMode.DexIndexed;
     Path outputPath = null;
     Path pgMapOutput = null;
     Path desugaredLibJson = null;
+    Path partialIncludeFile = null;
+    Path partialExcludeFile = null;
     StringConsumer desugaredLibKeepRuleConsumer = null;
     CompilationMode compilationMode = CompilationMode.RELEASE;
     List<Path> program = new ArrayList<>();
@@ -205,6 +212,16 @@
               desugaredLibKeepRuleConsumer = new StringConsumer.FileConsumer(Paths.get(operand));
               break;
             }
+          case "--partial-include":
+            {
+              partialIncludeFile = Paths.get(operand);
+              break;
+            }
+          case "--partial-exclude":
+            {
+              partialExcludeFile = Paths.get(operand);
+              break;
+            }
           case "--threads":
             {
               threads = Integer.parseInt(operand);
@@ -342,6 +359,15 @@
     if (pgMapOutput != null) {
       commandBuilder.setProguardMapOutputPath(pgMapOutput);
     }
+    if (partialIncludeFile != null) {
+      String includePatterns = String.join(",", Files.readAllLines(partialIncludeFile));
+      String excludePatterns =
+          partialExcludeFile != null
+              ? String.join(",", Files.readAllLines(partialExcludeFile))
+              : null;
+      R8PartialDumpUtils.enableExperimentalPartialShrinking(
+          commandBuilder, includePatterns, excludePatterns);
+    }
     R8Command command = commandBuilder.build();
     if (threads != -1) {
       ExecutorService executor = Executors.newWorkStealingPool(threads);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index c9b09f8..64de864 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1026,8 +1026,8 @@
   private final LibraryDesugaringOptions libraryDesugaringOptions =
       new LibraryDesugaringOptions(this);
   private final MappingComposeOptions mappingComposeOptions = new MappingComposeOptions();
-  private final ArtProfileOptions artProfileOptions = new ArtProfileOptions(this);
-  private final StartupOptions startupOptions = new StartupOptions();
+  private ArtProfileOptions artProfileOptions = new ArtProfileOptions(this);
+  private StartupOptions startupOptions = new StartupOptions(this);
   private final InstrumentationOptions instrumentationOptions;
   public R8PartialCompilationConfiguration partialCompilationConfiguration =
       R8PartialCompilationConfiguration.disabledConfiguration();
@@ -1127,10 +1127,18 @@
     return artProfileOptions;
   }
 
+  public void setArtProfileOptions(ArtProfileOptions artProfileOptions) {
+    this.artProfileOptions = artProfileOptions;
+  }
+
   public StartupOptions getStartupOptions() {
     return startupOptions;
   }
 
+  public void setStartupOptions(StartupOptions startupOptions) {
+    this.startupOptions = startupOptions;
+  }
+
   public InstrumentationOptions getInstrumentationOptions() {
     return instrumentationOptions;
   }
@@ -2114,6 +2122,7 @@
         System.getProperty("com.android.tools.r8.enableKeepAnnotations") != null;
     public boolean reverseClassSortingForDeterminism = false;
 
+    public boolean enableAutoCloseableDesugaring = false;
     public boolean enableNumberUnboxer = false;
     public boolean printNumberUnboxed = false;
     public boolean roundtripThroughLir = false;
@@ -2429,9 +2438,6 @@
 
     public boolean alwaysGenerateLambdaFactoryMethods = false;
 
-    // Work-in-progress optimization: b/145280859.
-    public boolean listIterationRewritingEnabled =
-        System.getProperty("com.android.tools.r8.enableListIterationRewriting") != null;
     public boolean listIterationRewritingRewriteInterfaces =
         "all".equals(System.getProperty("com.android.tools.r8.enableListIterationRewriting"));
     // Used by unit tests.
@@ -2690,6 +2696,17 @@
         && !canUseDefaultAndStaticInterfaceMethods();
   }
 
+  public boolean canHaveMissingImplementsAutoCloseableInterface() {
+    return getMinApiLevel().isLessThanOrEqualTo(AndroidApiLevel.V);
+  }
+
+  public boolean shouldDesugarAutoCloseable() {
+    return desugarState.isOn()
+        && getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)
+        && canHaveMissingImplementsAutoCloseableInterface()
+        && testing.enableAutoCloseableDesugaring;
+  }
+
   public boolean isSwitchRewritingEnabled() {
     return enableSwitchRewriting && !debug;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/OriginalSourceFiles.java b/src/main/java/com/android/tools/r8/utils/OriginalSourceFiles.java
index 7eca4e7..e5a332e 100644
--- a/src/main/java/com/android/tools/r8/utils/OriginalSourceFiles.java
+++ b/src/main/java/com/android/tools/r8/utils/OriginalSourceFiles.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
 import java.util.Map;
 
 /** Abstraction to allow removal of the source file content prior to collecting DEX items. */
@@ -39,11 +38,11 @@
   }
 
   /** Saved mapping of original source files prior to mutating the file on classes. */
-  public static OriginalSourceFiles fromMap(Map<DexType, DexString> map) {
+  public static OriginalSourceFiles fromMap(Map<DexProgramClass, DexString> map) {
     return new OriginalSourceFiles() {
       @Override
       public DexString getOriginalSourceFile(DexProgramClass clazz) {
-        return map.get(clazz.getType());
+        return map.get(clazz);
       }
     };
   }
diff --git a/src/main/java/com/android/tools/r8/utils/compiledump/R8PartialDumpUtils.java b/src/main/java/com/android/tools/r8/utils/compiledump/R8PartialDumpUtils.java
new file mode 100644
index 0000000..ba620c3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/compiledump/R8PartialDumpUtils.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.compiledump;
+
+import com.android.tools.r8.R8Command;
+
+public class R8PartialDumpUtils {
+
+  public static void enableExperimentalPartialShrinking(
+      R8Command.Builder builder, String includePatterns, String excludePatterns) {
+    builder.enableExperimentalPartialShrinking(includePatterns, excludePatterns);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
index fc1029f..bcfdb10 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.CfLineToMethodMapper;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OriginalSourceFiles;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.positions.MappedPositionToClassNameMapperBuilder.MappedPositionToClassNamingBuilder;
@@ -99,7 +100,6 @@
         || newMapVersion.isUnknown();
   }
 
-  @SuppressWarnings("ReferenceEquality")
   public static ClassNameMapper run(
       AppView<?> appView,
       AndroidApp inputApp,
@@ -119,60 +119,16 @@
 
     // Collect which files contain which classes that need to have their line numbers optimized.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
-          groupMethodsByRenamedName(appView, clazz);
-
-      MappedPositionToClassNamingBuilder classNamingBuilder = builder.addClassNaming(clazz);
-
-      // Process methods ordered by renamed name.
-      List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
-      renamedMethodNames.sort(DexString::compareTo);
-      for (DexString methodName : renamedMethodNames) {
-        List<ProgramMethod> methods = methodsByRenamedName.get(methodName);
-        if (methods.size() > 1) {
-          // If there are multiple methods with the same name (overloaded) then sort them for
-          // deterministic behaviour: the algorithm will assign new line numbers in this order.
-          // Methods with different names can share the same line numbers, that's why they don't
-          // need to be sorted.
-          // If we are compiling to DEX we will try to not generate overloaded names. This saves
-          // space by allowing more debug-information to be canonicalized. If we have overloaded
-          // methods, we either did not rename them, we renamed them according to a supplied map or
-          // they may be bridges for interface methods with covariant return types.
-          sortMethods(methods);
-          assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
-        }
-
-        PositionRemapper positionRemapper =
-            PositionRemapper.getPositionRemapper(appView, cfLineToMethodMapper);
-
-        for (ProgramMethod method : methods) {
-          DexEncodedMethod definition = method.getDefinition();
-          if (methodName == method.getName()
-              && !mustHaveResidualDebugInfo(appView.options(), definition)
-              && !definition.isD8R8Synthesized()
-              && methods.size() <= 1) {
-            continue;
-          }
-          positionRemapper.setCurrentMethod(definition);
-          List<MappedPosition> mappedPositions;
-          int pcEncodingCutoff =
-              methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
-          boolean canUseDexPc = pcEncodingCutoff > 0;
-          if (definition.getCode() != null
-              && (definition.getCode().isCfCode() || definition.getCode().isDexCode())
-              && !appView.isCfByteCodePassThrough(method)) {
-            mappedPositions =
-                positionToMappedRangeMapper.getMappedPositions(
-                    method, positionRemapper, methods.size() > 1, canUseDexPc, pcEncodingCutoff);
-          } else {
-            mappedPositions = new ArrayList<>();
-          }
-
-          classNamingBuilder.addMappedPositions(
-              method, mappedPositions, positionRemapper, canUseDexPc);
-        } // for each method of the group
-      } // for each method group, grouped by name
-    } // for each class
+      if (shouldRun(clazz, appView)) {
+        runForClass(
+            clazz,
+            appView,
+            representation,
+            builder,
+            cfLineToMethodMapper,
+            positionToMappedRangeMapper);
+      }
+    }
 
     // Update all the debug-info objects.
     positionToMappedRangeMapper.updateDebugInfoInCodeObjects();
@@ -180,6 +136,77 @@
     return builder.build();
   }
 
+  private static boolean shouldRun(DexProgramClass clazz, AppView<?> appView) {
+    InternalOptions options = appView.options();
+    if (options.partialSubCompilationConfiguration == null) {
+      return true;
+    } else {
+      return !options.partialSubCompilationConfiguration.asR8().hasD8DefinitionFor(clazz.getType());
+    }
+  }
+
+  private static void runForClass(
+      DexProgramClass clazz,
+      AppView<?> appView,
+      DebugRepresentationPredicate representation,
+      MappedPositionToClassNameMapperBuilder builder,
+      CfLineToMethodMapper cfLineToMethodMapper,
+      PositionToMappedRangeMapper positionToMappedRangeMapper) {
+    IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
+        groupMethodsByRenamedName(appView, clazz);
+
+    MappedPositionToClassNamingBuilder classNamingBuilder = builder.addClassNaming(clazz);
+
+    // Process methods ordered by renamed name.
+    List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
+    renamedMethodNames.sort(DexString::compareTo);
+    for (DexString methodName : renamedMethodNames) {
+      List<ProgramMethod> methods = methodsByRenamedName.get(methodName);
+      if (methods.size() > 1) {
+        // If there are multiple methods with the same name (overloaded) then sort them for
+        // deterministic behaviour: the algorithm will assign new line numbers in this order.
+        // Methods with different names can share the same line numbers, that's why they don't
+        // need to be sorted.
+        // If we are compiling to DEX we will try to not generate overloaded names. This saves
+        // space by allowing more debug-information to be canonicalized. If we have overloaded
+        // methods, we either did not rename them, we renamed them according to a supplied map or
+        // they may be bridges for interface methods with covariant return types.
+        sortMethods(methods);
+        assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
+      }
+
+      PositionRemapper positionRemapper =
+          PositionRemapper.getPositionRemapper(appView, cfLineToMethodMapper);
+
+      for (ProgramMethod method : methods) {
+        DexEncodedMethod definition = method.getDefinition();
+        if (method.getName().isIdenticalTo(methodName)
+            && !mustHaveResidualDebugInfo(appView.options(), definition)
+            && !definition.isD8R8Synthesized()
+            && methods.size() <= 1) {
+          continue;
+        }
+        positionRemapper.setCurrentMethod(definition);
+        List<MappedPosition> mappedPositions;
+        int pcEncodingCutoff =
+            methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
+        boolean canUseDexPc = pcEncodingCutoff > 0;
+        if (definition.getCode() != null
+            && (definition.getCode().isCfCode() || definition.getCode().isDexCode())
+            && !appView.isCfByteCodePassThrough(method)) {
+          mappedPositions =
+              positionToMappedRangeMapper.getMappedPositions(
+                  method, positionRemapper, methods.size() > 1, canUseDexPc, pcEncodingCutoff);
+        } else {
+          mappedPositions = new ArrayList<>();
+        }
+
+        classNamingBuilder.addMappedPositions(
+            method, mappedPositions, positionRemapper, canUseDexPc);
+      } // for each method of the group
+    } // for each method group, grouped by name
+  }
+
   @SuppressWarnings("ComplexBooleanConstant")
   private static boolean verifyMethodsAreKeptDirectlyOrIndirectly(
       AppView<?> appView, List<ProgramMethod> methods) {
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableDesugaringClassesPresentAtKitKat.java b/src/test/examplesJava21/autocloseable/AutoCloseableDesugaringClassesPresentAtKitKat.java
new file mode 100644
index 0000000..a6c9881
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableDesugaringClassesPresentAtKitKat.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryTypeRewriter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper;
+import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.nio.file.Path;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class AutoCloseableDesugaringClassesPresentAtKitKat extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    InternalOptions options = new InternalOptions();
+    Path androidJarK = ToolHelper.getAndroidJar(AndroidApiLevel.K);
+    AndroidApp app = AndroidApp.builder().addProgramFile(androidJarK).build();
+    DirectMappedDexApplication libHolder =
+        new ApplicationReader(app, options, Timing.empty()).read().toDirect();
+    AppInfo initialAppInfo =
+        AppInfo.createInitialAppInfo(libHolder, GlobalSyntheticsStrategy.forNonSynthesizing());
+    AppView<AppInfo> appView =
+        AppView.createForD8(initialAppInfo, DesugaredLibraryTypeRewriter.empty(), Timing.empty());
+
+    AutoCloseableRetargeterHelper data =
+        new AutoCloseableRetargeterHelper(AndroidApiLevel.B, appView.dexItemFactory());
+    Set<DexType> missing = Sets.newIdentityHashSet();
+    for (DexType dexType : data.superTargetsToRewrite()) {
+      if (appView.definitionFor(dexType) == null) {
+        missing.add(dexType);
+      }
+    }
+    assertEquals(1, missing.size());
+    // ForkJoinPool is missing at Android Api level 19 but that's ok since it implements
+    // ExecutorService.close in a more optimized way. We rely on ExecutorService for the
+    // emulated dispatch.
+    assertEquals(
+        options.dexItemFactory().javaUtilConcurrentForkJoinPoolType, missing.iterator().next());
+  }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTest.java
new file mode 100644
index 0000000..5fca53f
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTest.java
@@ -0,0 +1,271 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.ContentProviderClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.DrmManagerClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaMetadataRetriever;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.TypedArray;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+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 AutoCloseableRetargeterAndroidSubtypeTest extends AbstractBackportTest {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public AutoCloseableRetargeterAndroidSubtypeTest(TestParameters parameters) throws IOException {
+    super(
+        parameters,
+        getTestRunner(),
+        ImmutableList.of(
+            getTestRunner(),
+            getTransformedSubclass(
+                ContentProviderClientOverride.class,
+                DexItemFactory.androidContentContentProviderClientDescriptorString),
+            getTransformedSubclass(
+                ContentProviderClientSub.class,
+                DexItemFactory.androidContentContentProviderClientDescriptorString),
+            getTransformedSubclass(
+                DrmManagerClientOverride.class,
+                DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+            getTransformedSubclass(
+                DrmManagerClientSub.class,
+                DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+            getTransformedSubclass(
+                MediaMetadataRetrieverOverride.class,
+                DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+            getTransformedSubclass(
+                MediaMetadataRetrieverSub.class,
+                DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+            getTransformedSubclass(
+                TypedArrayOverride.class,
+                DexItemFactory.androidContentResTypedArrayDescriptorString),
+            getTransformedSubclass(
+                TypedArraySub.class, DexItemFactory.androidContentResTypedArrayDescriptorString)));
+
+    // The constructor is used by the test and release has been available since API 5 and is the
+    // method close is rewritten to.
+    ignoreInvokes("<init>");
+    ignoreInvokes("release");
+
+    registerTarget(AndroidApiLevel.B, 4);
+  }
+
+  @Override
+  protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+    d8TestBuilder.addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true);
+  }
+
+  @Override
+  protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
+    super.configureProgram(builder);
+    if (builder.isJvmTestBuilder()) {
+      builder.addProgramClassFileData(getAutoCloseableAndroidClassData(parameters));
+    } else {
+      builder
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+          .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters));
+    }
+  }
+
+  @Override
+  protected void configure(D8TestCompileResult builder) throws Exception {
+    if (parameters.isDexRuntime()) {
+      builder.addBootClasspathFiles(compileAutoCloseableAndroidLibraryClasses(this, parameters));
+    } else {
+      builder.addRunClasspathClassFileData(getAutoCloseableAndroidClassData(parameters));
+    }
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addStrippedOuter(getClass())
+        .apply(this::configureProgram)
+        .run(parameters.getRuntime(), getTestClassName())
+        // Fails when not desugared.
+        .assertFailureWithErrorThatMatches(containsString("close should not be called"));
+  }
+
+  private static byte[] getTransformedSubclass(Class<?> sub, String superDesc) throws IOException {
+    return transformer(sub)
+        .setSuper(superDesc)
+        .transformMethodInsnInMethod(
+            "<init>",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              assert name.equals("<init>");
+              visitor.visitMethodInsn(
+                  opcode,
+                  DescriptorUtils.descriptorToInternalName(superDesc),
+                  name,
+                  descriptor,
+                  isInterface);
+            })
+        .transformMethodInsnInMethod(
+            "close",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("close")) {
+                visitor.visitMethodInsn(
+                    opcode,
+                    DescriptorUtils.descriptorToInternalName(superDesc),
+                    name,
+                    descriptor,
+                    isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .clearNest()
+        .transform();
+  }
+
+  private static byte[] getTestRunner() throws IOException {
+    return transformer(TestRunner.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(ContentProviderClient.class),
+            DexItemFactory.androidContentContentProviderClientDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(DrmManagerClient.class),
+            DexItemFactory.androidDrmDrmManagerClientDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(MediaMetadataRetriever.class),
+            DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(TypedArray.class),
+            DexItemFactory.androidContentResTypedArrayDescriptorString)
+        .clearNest()
+        .transform();
+  }
+
+  public static class ContentProviderClientOverride extends ContentProviderClient {
+
+    public void close() {
+      super.close();
+      System.out.println("close content provider");
+    }
+  }
+
+  public static class ContentProviderClientSub extends ContentProviderClient {}
+
+  public static class DrmManagerClientOverride extends DrmManagerClient {
+
+    public void close() {
+      super.close();
+      System.out.println("close drm manager");
+    }
+  }
+
+  public static class DrmManagerClientSub extends DrmManagerClient {}
+
+  public static class MediaMetadataRetrieverOverride extends MediaMetadataRetriever {
+
+    public void close() {
+      super.close();
+      System.out.println("close media metadata");
+    }
+  }
+
+  public static class MediaMetadataRetrieverSub extends MediaMetadataRetriever {}
+
+  public static class TypedArrayOverride extends TypedArray {
+
+    public void close() {
+      super.close();
+      System.out.println("close typed array");
+    }
+  }
+
+  public static class TypedArraySub extends TypedArray {}
+
+  public static class TestRunner extends MiniAssert {
+
+    public static void main(String[] args) {
+      contentProviderClient();
+      drmManagerClient();
+      mediaMetadataRetriever();
+      typedArray();
+    }
+
+    private static void contentProviderClient() {
+      ContentProviderClientSub cpcSub = new ContentProviderClientSub();
+      MiniAssert.assertFalse(cpcSub.wasClosed);
+      cpcSub.close();
+      MiniAssert.assertTrue(cpcSub.wasClosed);
+
+      ContentProviderClientOverride cpcOverride = new ContentProviderClientOverride();
+      MiniAssert.assertFalse(cpcOverride.wasClosed);
+      cpcOverride.close();
+      MiniAssert.assertTrue(cpcOverride.wasClosed);
+    }
+
+    public static void drmManagerClient() {
+      DrmManagerClientSub drmMC = new DrmManagerClientSub();
+      MiniAssert.assertFalse(drmMC.wasClosed);
+      drmMC.close();
+      MiniAssert.assertTrue(drmMC.wasClosed);
+
+      DrmManagerClientOverride drmMCOverride = new DrmManagerClientOverride();
+      MiniAssert.assertFalse(drmMCOverride.wasClosed);
+      drmMCOverride.close();
+      MiniAssert.assertTrue(drmMCOverride.wasClosed);
+    }
+
+    public static void mediaMetadataRetriever() {
+      MediaMetadataRetrieverSub mmrSub = new MediaMetadataRetrieverSub();
+      MiniAssert.assertFalse(mmrSub.wasClosed);
+      mmrSub.close();
+      MiniAssert.assertTrue(mmrSub.wasClosed);
+
+      MediaMetadataRetrieverOverride mmrOverride = new MediaMetadataRetrieverOverride();
+      MiniAssert.assertFalse(mmrOverride.wasClosed);
+      mmrOverride.close();
+      MiniAssert.assertTrue(mmrOverride.wasClosed);
+    }
+
+    public static void typedArray() {
+      TypedArraySub taSub = new TypedArraySub();
+      MiniAssert.assertFalse(taSub.wasClosed);
+      taSub.close();
+      MiniAssert.assertTrue(taSub.wasClosed);
+
+      TypedArrayOverride taOverride = new TypedArrayOverride();
+      MiniAssert.assertFalse(taOverride.wasClosed);
+      taOverride.close();
+      MiniAssert.assertTrue(taOverride.wasClosed);
+    }
+
+    // Forwards to MiniAssert to avoid having to make it public.
+    public static void doFail(String message) {
+      MiniAssert.fail(message);
+    }
+  }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTwrTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTwrTest.java
new file mode 100644
index 0000000..efad24f
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTwrTest.java
@@ -0,0 +1,330 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.ContentProviderClientApiLevel24;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.DrmManagerClientApiLevel24;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaDrmApiLevel28;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaMetadataRetrieverApiLevel29;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.TypedArrayAndroidApiLevel31;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+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 AutoCloseableRetargeterAndroidSubtypeTwrTest extends AbstractBackportTest {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public AutoCloseableRetargeterAndroidSubtypeTwrTest(TestParameters parameters)
+      throws IOException {
+    super(
+        parameters,
+        getTestRunner(),
+        ImmutableList.of(
+            getTestRunner(),
+            getTransformedSubclass(
+                ContentProviderClientOverride.class,
+                DexItemFactory.androidContentContentProviderClientDescriptorString),
+            getTransformedSubclass(
+                ContentProviderClientSub.class,
+                DexItemFactory.androidContentContentProviderClientDescriptorString),
+            getTransformedSubclass(
+                DrmManagerClientOverride.class,
+                DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+            getTransformedSubclass(
+                DrmManagerClientSub.class,
+                DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+            getTransformedSubclass(
+                MediaMetadataRetrieverOverride.class,
+                DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+            getTransformedSubclass(
+                MediaMetadataRetrieverSub.class,
+                DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+            getTransformedSubclass(
+                TypedArrayOverride.class,
+                DexItemFactory.androidContentResTypedArrayDescriptorString),
+            getTransformedSubclass(
+                TypedArraySub.class, DexItemFactory.androidContentResTypedArrayDescriptorString)));
+
+    // The constructor is used by the test and release has been available since API 5 and is the
+    // method close is rewritten to.
+    ignoreInvokes("<init>");
+    ignoreInvokes("release");
+
+    registerTarget(AndroidApiLevel.B, 5);
+  }
+
+  @Override
+  protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+    d8TestBuilder.addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true);
+  }
+
+  @Override
+  protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
+    super.configureProgram(builder);
+    if (builder.isJvmTestBuilder()) {
+      builder.addProgramClassFileData(getAutoCloseableAndroidClassData(parameters));
+    } else {
+      builder
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+          .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters));
+    }
+  }
+
+  @Override
+  protected void configure(D8TestCompileResult builder) throws Exception {
+    if (parameters.isDexRuntime()) {
+      builder.addBootClasspathFiles(compileAutoCloseableAndroidLibraryClasses(this, parameters));
+    } else {
+      builder.addRunClasspathClassFileData(getAutoCloseableAndroidClassData(parameters));
+    }
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addStrippedOuter(getClass())
+        .apply(this::configureProgram)
+        .run(parameters.getRuntime(), getTestClassName())
+        // Fails when not desugared.
+        .assertFailureWithErrorThatMatches(containsString("close should not be called"));
+  }
+
+  private static byte[] getTransformedSubclass(Class<?> sub, String superDesc) throws IOException {
+    return transformer(sub)
+        .setSuper(superDesc)
+        .transformMethodInsnInMethod(
+            "<init>",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              assert name.equals("<init>");
+              visitor.visitMethodInsn(
+                  opcode,
+                  DescriptorUtils.descriptorToInternalName(superDesc),
+                  name,
+                  descriptor,
+                  isInterface);
+            })
+        .transformMethodInsnInMethod(
+            "close",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("close")) {
+                visitor.visitMethodInsn(
+                    opcode,
+                    DescriptorUtils.descriptorToInternalName(superDesc),
+                    name,
+                    descriptor,
+                    isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .clearNest()
+        .transform();
+  }
+
+  private static byte[] getTestRunner() throws IOException {
+    return transformer(TestRunner.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(ContentProviderClientApiLevel24.class),
+            DexItemFactory.androidContentContentProviderClientDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(DrmManagerClientApiLevel24.class),
+            DexItemFactory.androidDrmDrmManagerClientDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(MediaMetadataRetrieverApiLevel29.class),
+            DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(TypedArrayAndroidApiLevel31.class),
+            DexItemFactory.androidContentResTypedArrayDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(MediaDrmApiLevel28.class),
+            DexItemFactory.androidMediaMediaDrmDescriptorString)
+        .clearNest()
+        .transform();
+  }
+
+  public static class ContentProviderClientOverride extends ContentProviderClientApiLevel24 {
+
+    public void close() {
+      super.close();
+      System.out.println("close content provider");
+    }
+  }
+
+  public static class ContentProviderClientSub extends ContentProviderClientApiLevel24 {}
+
+  public static class DrmManagerClientOverride extends DrmManagerClientApiLevel24 {
+
+    public void close() {
+      super.close();
+      System.out.println("close drm manager");
+    }
+  }
+
+  public static class DrmManagerClientSub extends DrmManagerClientApiLevel24 {}
+
+  public static class MediaMetadataRetrieverOverride extends MediaMetadataRetrieverApiLevel29 {
+
+    public void close() {
+      super.close();
+      System.out.println("close media metadata");
+    }
+  }
+
+  public static class MediaMetadataRetrieverSub extends MediaMetadataRetrieverApiLevel29 {}
+
+  public static class TypedArrayOverride extends TypedArrayAndroidApiLevel31 {
+
+    public void close() {
+      super.close();
+      System.out.println("close typed array");
+    }
+  }
+
+  public static class TypedArraySub extends TypedArrayAndroidApiLevel31 {}
+
+  public static class TestRunner extends MiniAssert {
+
+    public static void main(String[] args) {
+      raw();
+      contentProviderClient();
+      drmManagerClient();
+      mediaMetadataRetriever();
+      typedArray();
+    }
+
+    private static void raw() {
+      ContentProviderClientApiLevel24[] box1 = new ContentProviderClientApiLevel24[1];
+      try (ContentProviderClientApiLevel24 val = new ContentProviderClientApiLevel24()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box1[0] = val;
+      }
+      MiniAssert.assertTrue(box1[0].wasClosed);
+
+      DrmManagerClientApiLevel24[] box2 = new DrmManagerClientApiLevel24[1];
+      try (DrmManagerClientApiLevel24 val = new DrmManagerClientApiLevel24()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box2[0] = val;
+      }
+      MiniAssert.assertTrue(box2[0].wasClosed);
+
+      MediaDrmApiLevel28[] box3 = new MediaDrmApiLevel28[1];
+      try (MediaDrmApiLevel28 val = new MediaDrmApiLevel28()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box3[0] = val;
+      }
+      MiniAssert.assertTrue(box3[0].wasClosed);
+
+      MediaMetadataRetrieverApiLevel29[] box4 = new MediaMetadataRetrieverApiLevel29[1];
+      try (MediaMetadataRetrieverApiLevel29 val = new MediaMetadataRetrieverApiLevel29()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box4[0] = val;
+      }
+      MiniAssert.assertTrue(box4[0].wasClosed);
+
+      TypedArrayAndroidApiLevel31[] box5 = new TypedArrayAndroidApiLevel31[1];
+      try (TypedArrayAndroidApiLevel31 val = new TypedArrayAndroidApiLevel31()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box5[0] = val;
+      }
+      MiniAssert.assertTrue(box5[0].wasClosed);
+    }
+
+    private static void contentProviderClient() {
+      ContentProviderClientSub[] box1 = new ContentProviderClientSub[1];
+      try (ContentProviderClientSub val = new ContentProviderClientSub()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box1[0] = val;
+      }
+      MiniAssert.assertTrue(box1[0].wasClosed);
+
+      ContentProviderClientOverride[] box2 = new ContentProviderClientOverride[1];
+      try (ContentProviderClientOverride val = new ContentProviderClientOverride()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box2[0] = val;
+      }
+      MiniAssert.assertTrue(box2[0].wasClosed);
+    }
+
+    public static void drmManagerClient() {
+      DrmManagerClientSub[] box1 = new DrmManagerClientSub[1];
+      try (DrmManagerClientSub val = new DrmManagerClientSub()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box1[0] = val;
+      }
+      MiniAssert.assertTrue(box1[0].wasClosed);
+
+      DrmManagerClientOverride[] box2 = new DrmManagerClientOverride[1];
+      try (DrmManagerClientOverride val = new DrmManagerClientOverride()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box2[0] = val;
+      }
+      MiniAssert.assertTrue(box2[0].wasClosed);
+    }
+
+    public static void mediaMetadataRetriever() {
+      MediaMetadataRetrieverSub[] box1 = new MediaMetadataRetrieverSub[1];
+      try (MediaMetadataRetrieverSub val = new MediaMetadataRetrieverSub()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box1[0] = val;
+      }
+      MiniAssert.assertTrue(box1[0].wasClosed);
+
+      MediaMetadataRetrieverOverride[] box2 = new MediaMetadataRetrieverOverride[1];
+      try (MediaMetadataRetrieverOverride val = new MediaMetadataRetrieverOverride()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box2[0] = val;
+      }
+      MiniAssert.assertTrue(box2[0].wasClosed);
+    }
+
+    public static void typedArray() {
+      TypedArraySub[] box1 = new TypedArraySub[1];
+      try (TypedArraySub val = new TypedArraySub()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box1[0] = val;
+      }
+      MiniAssert.assertTrue(box1[0].wasClosed);
+
+      TypedArrayOverride[] box2 = new TypedArrayOverride[1];
+      try (TypedArrayOverride val = new TypedArrayOverride()) {
+        MiniAssert.assertFalse(val.wasClosed);
+        box2[0] = val;
+      }
+      MiniAssert.assertTrue(box2[0].wasClosed);
+    }
+
+    // Forwards to MiniAssert to avoid having to make it public.
+    public static void doFail(String message) {
+      MiniAssert.fail(message);
+    }
+  }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidTest.java
new file mode 100644
index 0000000..e68e264
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidTest.java
@@ -0,0 +1,160 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.ContentProviderClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.DrmManagerClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaDrm;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaMetadataRetriever;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.TypedArray;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+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 AutoCloseableRetargeterAndroidTest extends AbstractBackportTest {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public AutoCloseableRetargeterAndroidTest(TestParameters parameters) throws IOException {
+    super(parameters, getTestRunner(), ImmutableList.of(getTestRunner()));
+
+    // The constructor is used by the test and release has been available since API 5 and is the
+    // method close is rewritten to.
+    ignoreInvokes("<init>");
+    ignoreInvokes("release");
+
+    registerTarget(AndroidApiLevel.B, 5);
+  }
+
+  @Override
+  protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+    d8TestBuilder.addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true);
+  }
+
+  @Override
+  protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
+    super.configureProgram(builder);
+    if (builder.isJvmTestBuilder()) {
+      builder.addProgramClassFileData(getAutoCloseableAndroidClassData(parameters));
+    } else {
+      builder
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+          .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters));
+    }
+  }
+
+  @Override
+  protected void configure(D8TestCompileResult builder) throws Exception {
+    if (parameters.isDexRuntime()) {
+      builder.addBootClasspathFiles(compileAutoCloseableAndroidLibraryClasses(this, parameters));
+    } else {
+      builder.addRunClasspathClassFileData(getAutoCloseableAndroidClassData(parameters));
+    }
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addStrippedOuter(getClass())
+        .apply(this::configureProgram)
+        .run(parameters.getRuntime(), getTestClassName())
+        // Fails when not desugared.
+        .assertFailureWithErrorThatMatches(containsString("close should not be called"));
+  }
+
+  private static byte[] getTestRunner() throws IOException {
+    return transformer(TestRunner.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(ContentProviderClient.class),
+            DexItemFactory.androidContentContentProviderClientDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(DrmManagerClient.class),
+            DexItemFactory.androidDrmDrmManagerClientDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(MediaDrm.class), DexItemFactory.androidMediaMediaDrmDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(MediaMetadataRetriever.class),
+            DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(TypedArray.class),
+            DexItemFactory.androidContentResTypedArrayDescriptorString)
+        .clearNest()
+        .transform();
+  }
+
+  public static class TestRunner extends MiniAssert {
+
+    public static void main(String[] args) {
+      contentProviderClient();
+      drmManagerClient();
+      mediaDrm();
+      mediaMetadataRetriever();
+      typedArray();
+    }
+
+    private static void contentProviderClient() {
+      ContentProviderClient contentProviderClient = new ContentProviderClient();
+      MiniAssert.assertFalse(contentProviderClient.wasClosed);
+      // Loop as regression test for b/276874854.
+      for (int i = 0; i < 2; i++) {
+        contentProviderClient.close();
+        MiniAssert.assertTrue(contentProviderClient.wasClosed);
+      }
+    }
+
+    public static void drmManagerClient() {
+      DrmManagerClient drmManagerClient = new DrmManagerClient();
+      MiniAssert.assertFalse(drmManagerClient.wasClosed);
+      drmManagerClient.close();
+      MiniAssert.assertTrue(drmManagerClient.wasClosed);
+    }
+
+    public static void mediaDrm() {
+      MediaDrm mediaDrm = new MediaDrm();
+      MiniAssert.assertFalse(mediaDrm.wasClosed);
+      mediaDrm.close();
+      MiniAssert.assertTrue(mediaDrm.wasClosed);
+    }
+
+    public static void mediaMetadataRetriever() {
+      MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
+      MiniAssert.assertFalse(mediaMetadataRetriever.wasClosed);
+      mediaMetadataRetriever.close();
+      MiniAssert.assertTrue(mediaMetadataRetriever.wasClosed);
+    }
+
+    public static void typedArray() {
+      TypedArray typedArray = new TypedArray();
+      MiniAssert.assertFalse(typedArray.wasClosed);
+      typedArray.close();
+      MiniAssert.assertTrue(typedArray.wasClosed);
+    }
+  }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceSubtypeTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceSubtypeTest.java
new file mode 100644
index 0000000..d9c567c
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceSubtypeTest.java
@@ -0,0 +1,349 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AutoCloseableRetargeterExecutorServiceSubtypeTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntime(CfVm.JDK21)
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+        .build();
+  }
+
+  public static String EXPECTED_OUTPUT =
+      StringUtils.lines("close", "close", "close", "close", "close", "close", "close", "SUCCESS");
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm(parameters)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+        .setMinApi(parameters)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+        .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+        .compile()
+        .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+        .inspect(this::assertCloseMethodsAndTags)
+        .run(
+            parameters.getRuntime(),
+            Main.class,
+            String.valueOf(parameters.getApiLevel().getLevel()))
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private void assertCloseMethodsAndTags(CodeInspector inspector) {
+    for (Class<? extends ExecutorService> clazz :
+        ImmutableList.of(
+            PrintForkJoinPool.class,
+            OverrideForkJoinPool.class,
+            Executor1.class,
+            Executor2.class)) {
+      ClassSubject subj = inspector.clazz(clazz);
+      Assert.assertTrue(subj.isPresent());
+      Assert.assertTrue(subj.allMethods().stream().anyMatch(m -> m.getFinalName().equals("close")));
+      Assert.assertTrue(
+          subj.getDexProgramClass()
+              .getInterfaces()
+              .contains(inspector.getFactory().autoCloseableType));
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(Main.class)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addInliningAnnotations()
+        .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+        .setMinApi(parameters)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+        .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+        .compile()
+        .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+        .run(
+            parameters.getRuntime(),
+            Main.class,
+            String.valueOf(parameters.getApiLevel().getLevel()))
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  public static class PrintForkJoinPool extends ForkJoinPool {
+
+    public void close() {
+      super.close();
+      System.out.println("close");
+    }
+  }
+
+  public static class OverrideForkJoinPool extends ForkJoinPool {}
+
+  public static class OverrideForkJoinPool2 extends OverrideForkJoinPool {}
+
+  public static class Executor1 implements ExecutorService {
+
+    boolean done = false;
+
+    @Override
+    public void shutdown() {
+      done = true;
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+      done = true;
+      return null;
+    }
+
+    @Override
+    public boolean isShutdown() {
+      return done;
+    }
+
+    @Override
+    public boolean isTerminated() {
+      return done;
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+      return true;
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+      return null;
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable task, T result) {
+      return null;
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+      return null;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException {
+      return null;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(
+        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException {
+      return null;
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException, ExecutionException {
+      return null;
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException, ExecutionException, TimeoutException {
+      return null;
+    }
+
+    @Override
+    public void execute(Runnable command) {}
+  }
+
+  public static class Executor2 implements ExecutorService {
+
+    boolean done = false;
+
+    @Override
+    public void shutdown() {
+      done = true;
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+      done = true;
+      return null;
+    }
+
+    @Override
+    public boolean isShutdown() {
+      return done;
+    }
+
+    @Override
+    public boolean isTerminated() {
+      return done;
+    }
+
+    public void close() {
+      ExecutorService.super.close();
+      System.out.println("close");
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+      return true;
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+      return null;
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable task, T result) {
+      return null;
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+      return null;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException {
+      return null;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(
+        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException {
+      return null;
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException, ExecutionException {
+      return null;
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException, ExecutionException, TimeoutException {
+      return null;
+    }
+
+    @Override
+    public void execute(Runnable command) {}
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      PrintForkJoinPool forkJoinPool = new PrintForkJoinPool();
+      forkJoinPool.close();
+      forkJoinPool = new PrintForkJoinPool();
+      close(forkJoinPool);
+
+      ForkJoinPool forkJoinPool2 = new PrintForkJoinPool();
+      forkJoinPool2.close();
+
+      ExecutorService executorService = new PrintForkJoinPool();
+      executorService.close();
+      executorService = new PrintForkJoinPool();
+      closeExecutorService(executorService);
+
+      OverrideForkJoinPool overrideForkJoinPool = new OverrideForkJoinPool();
+      overrideForkJoinPool.close();
+      overrideForkJoinPool = new OverrideForkJoinPool();
+      close(overrideForkJoinPool);
+
+      ForkJoinPool overrideForkJoinPool1 = new OverrideForkJoinPool();
+      overrideForkJoinPool1.close();
+
+      executorService = new OverrideForkJoinPool();
+      executorService.close();
+      executorService = new OverrideForkJoinPool();
+      closeExecutorService(executorService);
+
+      OverrideForkJoinPool2 overrideForkJoinPool2 = new OverrideForkJoinPool2();
+      overrideForkJoinPool2.close();
+      overrideForkJoinPool2 = new OverrideForkJoinPool2();
+      close(overrideForkJoinPool2);
+
+      ForkJoinPool overrideForkJoinPool22 = new OverrideForkJoinPool2();
+      overrideForkJoinPool22.close();
+
+      executorService = new OverrideForkJoinPool2();
+      executorService.close();
+      executorService = new OverrideForkJoinPool2();
+      closeExecutorService(executorService);
+
+      Executor1 executor1 = new Executor1();
+      executor1.close();
+      Executor2 executor2 = new Executor2();
+      executor2.close();
+      ExecutorService executor11 = new Executor1();
+      close(executor11);
+      ExecutorService executor22 = new Executor2();
+      close(executor22);
+
+      System.out.println("SUCCESS");
+    }
+
+    @NeverInline
+    public static void closeExecutorService(ExecutorService ac) throws Exception {
+      ac.close();
+    }
+
+    @NeverInline
+    public static void close(AutoCloseable ac) throws Exception {
+      ac.close();
+    }
+  }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceTest.java
new file mode 100644
index 0000000..ae043fd
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AutoCloseableRetargeterExecutorServiceTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+        .build();
+  }
+
+  private static String EXPECTED_OUTPUT = StringUtils.lines("false", "true", "SUCCESS");
+  private static String EXPECTED_OUTPUT_24 =
+      StringUtils.lines("false", "true", "false", "false", "SUCCESS");
+
+  private String getExpectedOutput() {
+    return parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)
+        ? EXPECTED_OUTPUT
+        : EXPECTED_OUTPUT_24;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+        .setMinApi(parameters)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+        .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+        .compile()
+        .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+        .run(
+            parameters.getRuntime(),
+            Main.class,
+            String.valueOf(parameters.getApiLevel().getLevel()))
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(Main.class)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addInliningAnnotations()
+        .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+        .setMinApi(parameters)
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+        .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+        .compile()
+        .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+        .run(
+            parameters.getRuntime(),
+            Main.class,
+            String.valueOf(parameters.getApiLevel().getLevel()))
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      int api = Integer.parseInt(args[0]);
+
+      ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);
+      System.out.println(pool.isTerminated());
+      pool.close();
+      System.out.println(pool.isTerminated());
+
+      if (api >= 21) {
+        ForkJoinPool forkJoinPool = new ForkJoinPool();
+        forkJoinPool.close();
+        forkJoinPool = new ForkJoinPool();
+        close(forkJoinPool);
+
+        ExecutorService executorService = new ForkJoinPool();
+        executorService.close();
+        executorService = new ForkJoinPool();
+        closeExecutorService(executorService);
+      }
+
+      if (api >= 24) {
+        ForkJoinPool commonPool = ForkJoinPool.commonPool();
+        System.out.println(commonPool.isTerminated());
+        commonPool.close();
+        // Common pool should never terminate.
+        System.out.println(commonPool.isTerminated());
+      }
+
+      System.out.println("SUCCESS");
+    }
+
+    @NeverInline
+    public static void closeExecutorService(ExecutorService ac) throws Exception {
+      ac.close();
+    }
+
+    @NeverInline
+    public static void close(AutoCloseable ac) throws Exception {
+      ac.close();
+    }
+  }
+}
diff --git a/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java b/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
index c55a8e4..a57d54b 100644
--- a/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
+++ b/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
@@ -4,8 +4,14 @@
 
 package twr;
 
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.desugar.backports.AbstractBackportTest;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -33,6 +39,32 @@
     ignoreInvokes("isTerminated");
   }
 
+  @Override
+  protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
+    super.configureProgram(builder);
+    if (builder.isJvmTestBuilder()) {
+      builder.addProgramClassFileData(getAutoCloseableAndroidClassData(parameters));
+    } else {
+      builder
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+          .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters));
+    }
+  }
+
+  @Override
+  protected void configure(D8TestCompileResult builder) throws Exception {
+    if (parameters.isDexRuntime()) {
+      builder.addBootClasspathFiles(compileAutoCloseableAndroidLibraryClasses(this, parameters));
+    } else {
+      builder.addRunClasspathClassFileData(getAutoCloseableAndroidClassData(parameters));
+    }
+  }
+
+  @Override
+  public void testD8Cf() throws Exception {
+    super.testD8Cf();
+  }
+
   public static class Main {
 
     public static void main(String[] args) {
diff --git a/src/test/examplesJava21/twr/LookUpCloseResourceTest.java b/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
index 51028fe..64ebfd2 100644
--- a/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
+++ b/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
@@ -78,7 +78,7 @@
     for (DexProgramClass clazz : appInfoForMax.classes()) {
       OptionalBool contains =
           appInfoForMax.implementedInterfaces(clazz.getType()).contains(factory.autoCloseableType);
-      if (contains.isTrue() && clazz.getType() != factory.autoCloseableType) {
+      if (contains.isPossiblyTrue() && clazz.getType() != factory.autoCloseableType) {
         if (clazz.lookupVirtualMethod(close.withHolder(clazz.getType(), factory)) != null) {
           autoCloseableSubclassesWithOverride.add(clazz.getType());
         } else {
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index b217907..b3c22ce 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -298,7 +298,7 @@
             .allowUnusedProguardConfigurationRules()
             // TODO(b/222228826): Disallow unrecognized diagnostics and open interfaces.
             .allowDiagnosticMessages()
-            .addR8PartialOptionsModification(
+            .addR8PartialR8OptionsModification(
                 options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces());
   }
 
@@ -409,7 +409,7 @@
                               FileUtils.readTextFile(dump.getProguardConfigFile()),
                               "-shrinkunusedprotofields",
                               ""))
-                      .addR8PartialOptionsModification(
+                      .addR8PartialR8OptionsModification(
                           options -> {
                             options.apiModelingOptions().androidApiExtensionPackages =
                                 dumpProperties.getAndroidApiExtensionPackages();
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java
index 931a36c..6d86a37 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ComposeSamplesBenchmarks.java
@@ -158,7 +158,7 @@
         .allowUnnecessaryDontWarnWildcards()
         .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
-        .addR8PartialOptionsModification(
+        .addR8PartialR8OptionsModification(
             options -> options.getOpenClosedInterfacesOptions().disallowOpenInterfaces());
   }
 
diff --git a/src/test/java/com/android/tools/r8/dump/CompilerDumpTest.java b/src/test/java/com/android/tools/r8/dump/CompilerDumpTest.java
index 509f85f..9f4ff3c 100644
--- a/src/test/java/com/android/tools/r8/dump/CompilerDumpTest.java
+++ b/src/test/java/com/android/tools/r8/dump/CompilerDumpTest.java
@@ -3,15 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dump;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestBase;
+import com.android.tools.r8.R8PartialTestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.internal.CompilationTestBase;
 import com.android.tools.r8.utils.DumpInputFlags;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Lists;
+import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -19,7 +27,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class CompilerDumpTest extends TestBase {
+public class CompilerDumpTest extends CompilationTestBase {
 
   @Parameter() public TestParameters parameters;
 
@@ -78,10 +86,73 @@
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
+  @Test
+  public void testR8Partial() throws Exception {
+    parameters.assumeR8PartialTestParameters();
+    // Create an R8 partial dump.
+    Path dumpDirectory = temp.newFolder().toPath();
+    DumpInputFlags dumpInputFlags = DumpInputFlags.dumpToDirectory(dumpDirectory);
+    R8PartialTestCompileResult compileResult =
+        testForR8Partial(parameters.getBackend())
+            .addR8IncludedClasses(IncludedMain.class)
+            .addR8ExcludedClasses(ExcludedMain.class)
+            .addKeepMainRule(IncludedMain.class)
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters)
+            .addR8PartialOptionsModification(options -> options.setDumpInputFlags(dumpInputFlags))
+            .addR8PartialD8OptionsModification(options -> options.setDumpInputFlags(dumpInputFlags))
+            .addR8PartialR8OptionsModification(options -> options.setDumpInputFlags(dumpInputFlags))
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics
+                        .assertInfosMatch(
+                            diagnosticMessage(startsWith("Dumped compilation inputs to:")))
+                        .assertNoWarnings()
+                        .assertNoErrors());
+
+    // Verify that only one archive was dumped.
+    List<Path> dumpArchives = Files.list(dumpDirectory).collect(Collectors.toList());
+    assertEquals(1, dumpArchives.size());
+
+    Path dumpArchive = dumpArchives.iterator().next();
+
+    // Inspect the dump.
+    CompilerDump dump = CompilerDump.fromArchive(dumpArchive, temp.newFolder().toPath());
+    assertEquals(
+        Lists.newArrayList(IncludedMain.class.getTypeName()), dump.getR8PartialIncludePatterns());
+    assertEquals(
+        Lists.newArrayList(ExcludedMain.class.getTypeName()),
+        dump.getR8PartialExcludePatternsOrDefault(null));
+
+    // Compile the dump.
+    testForR8Partial(parameters.getBackend())
+        .applyCompilerDump(dump)
+        .compile()
+        .apply(
+            recompileResult ->
+                assertIdenticalApplications(compileResult.getApp(), recompileResult.getApp()))
+        .run(parameters.getRuntime(), IncludedMain.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
   static class TestClass {
 
     public static void main(String[] args) {
       System.out.println("Hello, world!");
     }
   }
+
+  static class IncludedMain {
+
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+    }
+  }
+
+  static class ExcludedMain {
+
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java
index 3d5a297..c0382b0 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java
@@ -1,11 +1,21 @@
 package com.android.tools.r8.ir.desugar.backports;
 
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.TimeUnit;
 
 public class ExecutorServiceMethods {
+  // Stub out android.os.Build$VERSION as it does not exist when building R8.
+  private static class AndroidOsBuildVersionStub {
+    public static int SDK_INT;
+  }
 
   public static void closeExecutorService(ExecutorService executorService) {
+    if (AndroidOsBuildVersionStub.SDK_INT > 23) {
+      if (executorService == ForkJoinPool.commonPool()) {
+        return;
+      }
+    }
     boolean terminated = executorService.isTerminated();
     if (!terminated) {
       executorService.shutdown();
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 96dd2e3..40d6b6d 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -169,7 +169,13 @@
   private static CfInstruction rewriteToAndroidOsBuildVersion(
       DexItemFactory itemFactory, CfInstruction instruction) {
     // Rewrite references to UnsafeStub to sun.misc.Unsafe.
-    if (instruction.isStaticFieldGet()) {
+    if (instruction.isStaticFieldGet()
+        && instruction
+            .asFieldInstruction()
+            .getField()
+            .getHolderType()
+            .toString()
+            .contains("Stub")) {
       CfStaticFieldRead fieldGet = instruction.asStaticFieldGet();
       return new CfStaticFieldRead(
           itemFactory.createField(
@@ -182,7 +188,10 @@
           .asFrame()
           .mapReferenceTypes(
               type -> {
-                throw new RuntimeException("Unexpected CfFrame instruction.");
+                if (type.toString().contains("Stub")) {
+                  throw new RuntimeException("Unexpected CfFrame instruction.");
+                }
+                return type;
               });
     }
     return instruction;
@@ -206,6 +215,12 @@
               .map(instruction -> rewriteToUnsafe(factory, instruction))
               .collect(Collectors.toList()));
     }
+    if (holderName.equals("ExecutorServiceMethods") && methodName.equals("closeExecutorService")) {
+      code.setInstructions(
+          code.getInstructions().stream()
+              .map(instruction -> rewriteToAndroidOsBuildVersion(factory, instruction))
+              .collect(Collectors.toList()));
+    }
     if (holderName.equals("AndroidOsBuildVersionMethods") && methodName.equals("getSdkIntFull")) {
       code.setInstructions(
           code.getInstructions().stream()
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationArtProfileTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationArtProfileTest.java
new file mode 100644
index 0000000..9af4f97
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationArtProfileTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2025, 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.partial;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.partial.PartialCompilationWithDefaultInterfaceMethodTest.Main;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PartialCompilationArtProfileTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+        .build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Partial(parameters.getBackend())
+        .addR8IncludedClasses(IncludedInterface.class)
+        .addR8ExcludedClasses(Main.class, ExcludedInterface.class)
+        .addArtProfileForRewriting(
+            ExternalArtProfile.builder()
+                .addMethodRule(
+                    Reference.methodFromMethod(IncludedInterface.class.getDeclaredMethod("foo")))
+                .addMethodRule(
+                    Reference.methodFromMethod(ExcludedInterface.class.getDeclaredMethod("bar")))
+                .build())
+        .setMinApi(parameters)
+        .compile()
+        .inspectResidualArtProfile(
+            (artProfileInspector, codeInspector) -> {
+              ClassSubject includedInterfaceClass, excludedInterfaceClass;
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                includedInterfaceClass = codeInspector.clazz(IncludedInterface.class);
+                excludedInterfaceClass = codeInspector.clazz(ExcludedInterface.class);
+              } else {
+                includedInterfaceClass =
+                    codeInspector.clazz(
+                        SyntheticItemsTestUtils.syntheticCompanionClass(IncludedInterface.class));
+                excludedInterfaceClass =
+                    codeInspector.clazz(
+                        SyntheticItemsTestUtils.syntheticCompanionClass(ExcludedInterface.class));
+              }
+              artProfileInspector
+                  .assertContainsClassRules(includedInterfaceClass, excludedInterfaceClass)
+                  .assertContainsMethodRules(
+                      includedInterfaceClass.uniqueMethodWithOriginalName("foo"),
+                      excludedInterfaceClass.uniqueMethodWithOriginalName("bar"))
+                  .assertContainsNoOtherRules();
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("IncludedInterface.foo()", "ExcludedInterface.bar()");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      IncludedInterface.foo();
+      ExcludedInterface.bar();
+    }
+  }
+
+  interface IncludedInterface {
+
+    static void foo() {
+      System.out.println("IncludedInterface.foo()");
+    }
+  }
+
+  interface ExcludedInterface {
+
+    static void bar() {
+      System.out.println("ExcludedInterface.bar()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicPreviewPatternsTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicPreviewPatternsTest.java
index de63504..5daa8d1 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicPreviewPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicPreviewPatternsTest.java
@@ -138,10 +138,7 @@
         .addProgramClasses(ALL_CLASSES)
         .setR8PartialConfiguration(
             builder ->
-                builder
-                    .addJavaTypeIncludePattern(PKG1 + ".**")
-                    .addJavaTypeExcludePattern(PKG1 + ".A1")
-                    .addJavaTypeExcludePattern(PKG1 + ".A2"))
+                builder.addJavaTypeIncludePattern(PKG1 + ".**").excludeClasses(A1.class, A2.class))
         .compile()
         .inspect(
             inspector ->
@@ -163,7 +160,7 @@
                 builder
                     .addJavaTypeIncludePattern(PKG1 + ".**")
                     .addJavaTypeIncludePattern(PKG2 + ".**")
-                    .addJavaTypeExcludePattern(PKG2 + ".C1"))
+                    .excludeClasses(C1.class))
         .compile()
         .inspect(inspector -> assertTrue(inspector.hasExactlyProgramClasses(C1.class, Main.class)))
         .run(parameters.getRuntime(), Main.class, ALL_TYPE_NAMES)
@@ -186,7 +183,7 @@
                     .addJavaTypeIncludePattern(PKG1 + ".*")
                     .addJavaTypeIncludePattern(SUBPKG + ".*")
                     .addJavaTypeIncludePattern(PKG2 + ".*")
-                    .addJavaTypeExcludePattern(PKG1 + ".A1"))
+                    .excludeClasses(A1.class))
         .compile()
         .inspect(inspector -> assertTrue(inspector.hasExactlyProgramClasses(A1.class, Main.class)))
         .run(parameters.getRuntime(), Main.class, ALL_TYPE_NAMES)
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationD8LineNumberTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationD8LineNumberTest.java
new file mode 100644
index 0000000..57a67db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationD8LineNumberTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2025, 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.partial;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.R8PartialTestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PartialCompilationD8LineNumberTest extends TestBase {
+
+  private static StackTrace expectedD8StackTrace;
+  private static StackTrace expectedR8StackTrace;
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    // Get the expected stack traces by running on the JVM.
+    expectedD8StackTrace =
+        testForJvm(getStaticTemp())
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), ExcludedClass.class)
+            .assertFailureWithErrorThatThrows(RuntimeException.class)
+            .map(StackTrace::extractFromJvm);
+    expectedR8StackTrace =
+        testForJvm(getStaticTemp())
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), IncludedClass.class)
+            .assertFailureWithErrorThatThrows(RuntimeException.class)
+            .map(StackTrace::extractFromJvm);
+  }
+
+  @Test
+  public void test() throws Throwable {
+    parameters.assumeR8PartialTestParameters();
+    R8PartialTestCompileResult compileResult =
+        testForR8Partial(parameters.getBackend())
+            .addR8IncludedClasses(IncludedClass.class)
+            .addR8ExcludedClasses(ExcludedClass.class)
+            .addKeepMainRule(IncludedClass.class)
+            .setMinApi(parameters)
+            .compile()
+            .inspect(
+                inspector -> {
+                  ClassSubject includedClass = inspector.clazz(IncludedClass.class);
+                  assertThat(includedClass, isPresent());
+                  assertEquals(
+                      "SourceFile", includedClass.getDexProgramClass().getSourceFile().toString());
+
+                  ClassSubject excludedClass = inspector.clazz(ExcludedClass.class);
+                  assertThat(excludedClass, isPresent());
+                  assertEquals(
+                      "PartialCompilationD8LineNumberTest.java",
+                      excludedClass.getDexProgramClass().getSourceFile().toString());
+                })
+            .inspectProguardMap(
+                map -> {
+                  assertThat(map, containsString(IncludedClass.class.getTypeName()));
+                  assertThat(map, not(containsString(ExcludedClass.class.getTypeName())));
+                });
+
+    compileResult
+        .run(parameters.getRuntime(), ExcludedClass.class)
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectOriginalStackTrace(
+            stackTrace -> assertThat(stackTrace, isSame(expectedD8StackTrace)));
+
+    compileResult
+        .run(parameters.getRuntime(), IncludedClass.class)
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectOriginalStackTrace(
+            stackTrace -> assertThat(stackTrace, not(isSame(expectedD8StackTrace))))
+        .inspectStackTrace(stackTrace -> assertThat(stackTrace, isSame(expectedR8StackTrace)));
+  }
+
+  static class IncludedClass {
+
+    public static void main(String[] args) {
+      foo();
+    }
+
+    private static void foo() {
+      throw new RuntimeException();
+    }
+  }
+
+  static class ExcludedClass {
+
+    public static void main(String[] args) {
+      foo();
+    }
+
+    private static void foo() {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java
index c7dc4dc..860837a 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java
@@ -37,7 +37,7 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.function.Predicate;
+import java.util.function.Consumer;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -146,28 +146,33 @@
     Path tempDir = temp.newFolder().toPath();
 
     // Different sets of namespaces to shrink.
-    ImmutableMap<String, Predicate<String>> splits =
+    ImmutableMap<String, Consumer<R8PartialCompilationConfiguration.Builder>> splits =
         ImmutableMap.of(
-            "androidx", name -> name.startsWith("androidx."),
+            "androidx", configuration -> configuration.addJavaTypeIncludePattern("androidx.**"),
             "androidx_kotlin_and_kotlinx",
-                name ->
-                    name.startsWith("androidx.")
-                        || name.startsWith("kotlin.")
-                        || name.startsWith("kotlinx."),
+                configuration ->
+                    configuration
+                        .addJavaTypeIncludePattern("androidx.**")
+                        .addJavaTypeIncludePattern("kotlin.**")
+                        .addJavaTypeIncludePattern("kotlinx.**"),
             "more_libraries",
-                name ->
-                    name.startsWith("androidx.")
-                        || name.startsWith("kotlin.")
-                        || name.startsWith("kotlinx.")
-                        || name.startsWith("android.support.")
-                        || name.startsWith("io.ktor.")
-                        || name.startsWith("com.google.android.gms.")
-                        || name.startsWith("com.google.firebase."),
-            "all_but_app namespace", name -> !name.startsWith(appNamespace + "."));
+                configuration ->
+                    configuration
+                        .addJavaTypeIncludePattern("androidx.**")
+                        .addJavaTypeIncludePattern("kotlin.**")
+                        .addJavaTypeIncludePattern("kotlinx.**")
+                        .addJavaTypeIncludePattern("android.support.**")
+                        .addJavaTypeIncludePattern("io.ktor.**")
+                        .addJavaTypeIncludePattern("com.google.android.gms.**")
+                        .addJavaTypeIncludePattern("com.google.firebase.**"),
+            "all_but_app_namespace",
+                configuration ->
+                    configuration.includeAll().addJavaTypeExcludePattern(appNamespace + ".**"));
 
     // Compile with each set of namespaces to shrink and collect DEX size.
     Map<String, Pair<Long, Long>> dexSize = new LinkedHashMap<>();
-    for (Entry<String, Predicate<String>> entry : splits.entrySet()) {
+    for (Entry<String, Consumer<R8PartialCompilationConfiguration.Builder>> entry :
+        splits.entrySet()) {
       long size = runR8PartialAndL8(tempDir, dump, entry.getKey(), entry.getValue());
       dexSize.put(entry.getKey(), new Pair<>(size, 0L));
     }
@@ -205,11 +210,15 @@
   }
 
   private long runR8PartialAndL8(
-      Path tempDir, CompilerDump dump, String name, Predicate<String> isR8) throws Exception {
+      Path tempDir,
+      CompilerDump dump,
+      String name,
+      Consumer<R8PartialCompilationConfiguration.Builder> partialConfiguration)
+      throws Exception {
     Path tmp = tempDir.resolve(name);
     Files.createDirectory(tmp);
     Path output = tmp.resolve("tivix8.zip");
-    runR8Partial(tempDir, dump, output, isR8);
+    runR8Partial(tempDir, dump, output, partialConfiguration);
     Path l8Output = tmp.resolve("tivix8l8.zip");
     runL8(tmp, dump, output, l8Output);
     Box<Long> size = new Box<>(0L);
@@ -226,14 +235,16 @@
     return size.get();
   }
 
-  private void runR8Partial(Path tempDir, CompilerDump dump, Path output, Predicate<String> isR8)
+  private void runR8Partial(
+      Path tempDir,
+      CompilerDump dump,
+      Path output,
+      Consumer<R8PartialCompilationConfiguration.Builder> partialConfiguration)
       throws IOException, CompilationFailedException {
     testForR8Partial(parameters.getBackend())
-        .setR8PartialConfigurationJavaTypePredicate(isR8)
+        .setR8PartialConfiguration(partialConfiguration)
         .addOptionsModification(
             options -> {
-              options.partialCompilationConfiguration.setTempDir(tempDir);
-
               // For compiling nowonandroid.
               options.testing.allowUnnecessaryDontWarnWildcards = true;
               options.testing.allowUnusedDontWarnRules = true;
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationIfD8ClassPresentTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationIfD8ClassPresentTest.java
new file mode 100644
index 0000000..aae826f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationIfD8ClassPresentTest.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2025, 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.partial;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PartialCompilationIfD8ClassPresentTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    parameters.assumeR8PartialTestParameters();
+    testForR8Partial(parameters.getBackend())
+        .addR8IncludedClasses(Main.class)
+        .addR8ExcludedClasses(ExcludedClass.class)
+        .addKeepRules(
+            "-if class **Excluded** { public static void foo(); }",
+            "-keep class " + Main.class.getTypeName() + " {",
+            "  public static void main(java.lang.String[]);",
+            "}")
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      ExcludedClass.foo();
+    }
+  }
+
+  static class ExcludedClass {
+
+    public static void foo() {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationKeepAllowObfuscationBoundaryTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationKeepAllowObfuscationBoundaryTest.java
new file mode 100644
index 0000000..30bafec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationKeepAllowObfuscationBoundaryTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2025, 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.partial;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PartialCompilationKeepAllowObfuscationBoundaryTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    parameters.assumeR8PartialTestParameters();
+    testForR8Partial(parameters.getBackend())
+        .addR8IncludedClasses(IncludedClass.class)
+        .addR8ExcludedClasses(Main.class)
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject includedClassSubject = inspector.clazz(IncludedClass.class);
+              assertThat(includedClassSubject, isPresentAndRenamed());
+
+              MethodSubject fooMethodSubject =
+                  includedClassSubject.uniqueMethodWithOriginalName("foo");
+              assertThat(fooMethodSubject, isPresentAndRenamed());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      IncludedClass.foo();
+    }
+  }
+
+  static class IncludedClass {
+
+    static void foo() {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationStartupProfileTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationStartupProfileTest.java
new file mode 100644
index 0000000..aa101c2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationStartupProfileTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2025, 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.partial;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.StartupClassesNonStartupFractionDiagnostic;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.profile.ExternalStartupMethod;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PartialCompilationStartupProfileTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+        .build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Partial(parameters.getBackend())
+        .addR8IncludedClasses(IncludedInterface.class)
+        .addR8ExcludedClasses(Main.class, ExcludedInterface.class)
+        .allowDiagnosticInfoMessages()
+        .apply(
+            testBuilder -> StartupTestingUtils.addStartupProfile(testBuilder, getStartupProfile()))
+        .setMinApi(parameters)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertInfosMatch(
+                        diagnosticType(StartupClassesNonStartupFractionDiagnostic.class))
+                    .assertNoWarnings()
+                    .assertNoErrors())
+        .inspectMultiDex(
+            primaryDexInspector -> {
+              assertEquals(2, primaryDexInspector.allClasses().size());
+              ClassSubject includedInterfaceClass, excludedInterfaceClass;
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                includedInterfaceClass = primaryDexInspector.clazz(IncludedInterface.class);
+                excludedInterfaceClass = primaryDexInspector.clazz(ExcludedInterface.class);
+              } else {
+                includedInterfaceClass =
+                    primaryDexInspector.clazz(
+                        SyntheticItemsTestUtils.syntheticCompanionClass(IncludedInterface.class));
+                excludedInterfaceClass =
+                    primaryDexInspector.clazz(
+                        SyntheticItemsTestUtils.syntheticCompanionClass(ExcludedInterface.class));
+              }
+              assertThat(includedInterfaceClass, isPresent());
+              assertThat(excludedInterfaceClass, isPresent());
+            },
+            secondaryDexInspector -> {
+              assertEquals(
+                  parameters.canUseDefaultAndStaticInterfaceMethods() ? 1 : 2,
+                  secondaryDexInspector.allClasses().size());
+              assertThat(secondaryDexInspector.clazz(Main.class), isPresent());
+              assertThat(
+                  secondaryDexInspector.clazz(ExcludedInterface.class),
+                  isAbsentIf(parameters.canUseDefaultAndStaticInterfaceMethods()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("IncludedInterface.foo()", "ExcludedInterface.bar()");
+  }
+
+  private static List<ExternalStartupMethod> getStartupProfile() throws NoSuchMethodException {
+    return ImmutableList.of(
+        ExternalStartupMethod.builder()
+            .setMethodReference(
+                Reference.methodFromMethod(IncludedInterface.class.getDeclaredMethod("foo")))
+            .build(),
+        ExternalStartupMethod.builder()
+            .setMethodReference(
+                Reference.methodFromMethod(ExcludedInterface.class.getDeclaredMethod("bar")))
+            .build());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      IncludedInterface.foo();
+      ExcludedInterface.bar();
+    }
+  }
+
+  interface IncludedInterface {
+
+    static void foo() {
+      System.out.println("IncludedInterface.foo()");
+    }
+  }
+
+  interface ExcludedInterface {
+
+    static void bar() {
+      System.out.println("ExcludedInterface.bar()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java
index 4159691..aacdc98 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationWithDefaultInterfaceMethodTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.partial;
 
+import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.DEFAULT_METHOD_PREFIX;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbstract;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -61,7 +62,8 @@
                 assertThat(jCompanionClassSubject, isPresent());
 
                 MethodSubject jCompanionMethodSubject =
-                    jCompanionClassSubject.uniqueMethodWithOriginalName("m");
+                    jCompanionClassSubject.uniqueMethodWithOriginalName(
+                        DEFAULT_METHOD_PREFIX + "m");
                 assertThat(jCompanionMethodSubject, isPresent());
 
                 ClassSubject aClassSubject = inspector.clazz(A.class);
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
index 2670bee..5eead4f 100644
--- a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -162,7 +162,7 @@
 
   public static void addStartupProfile(
       TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder,
-      Collection<ExternalStartupItem> startupItems) {
+      Collection<? extends ExternalStartupItem> startupItems) {
     StartupProfileProvider startupProfileProvider =
         new StartupProfileProvider() {
           @Override
@@ -188,9 +188,12 @@
         };
     if (testBuilder.isD8TestBuilder()) {
       testBuilder.asD8TestBuilder().addStartupProfileProviders(startupProfileProvider);
-    } else {
+    } else if (testBuilder.isR8TestBuilder()) {
       assertTrue(testBuilder.isR8TestBuilder());
       testBuilder.asR8TestBuilder().addStartupProfileProviders(startupProfileProvider);
+    } else {
+      assertTrue(testBuilder.isR8PartialTestBuilder());
+      testBuilder.asR8PartialTestBuilder().addStartupProfileProviders(startupProfileProvider);
     }
   }
 
diff --git a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
index da2771f..25d4fab 100644
--- a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
@@ -46,6 +46,11 @@
   }
 
   @Override
+  public boolean isJvmTestBuilder() {
+    return true;
+  }
+
+  @Override
   JvmTestBuilder self() {
     return this;
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
index 7ac85b1..f32f03c 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
@@ -6,17 +6,18 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.dump.CompilerDump;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.partial.R8PartialCompilationConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 public class R8PartialTestBuilder
@@ -66,15 +67,6 @@
     return this;
   }
 
-  public R8PartialTestBuilder setR8PartialConfigurationJavaTypePredicate(
-      Predicate<String> include) {
-    assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
-        : "Overwriting configuration...?";
-    r8PartialConfiguration =
-        R8PartialCompilationConfiguration.builder().includeJavaType(include).build();
-    return self();
-  }
-
   public R8PartialTestBuilder setR8PartialConfiguration(
       Consumer<R8PartialCompilationConfiguration.Builder> consumer) {
     assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
@@ -163,14 +155,20 @@
             + "Did you mean addD8PartialOptionsModification or addR8PartialOptionsModification?");
   }
 
-  public R8PartialTestBuilder addD8PartialOptionsModification(Consumer<InternalOptions> consumer) {
+  public R8PartialTestBuilder addR8PartialOptionsModification(Consumer<InternalOptions> consumer) {
+    return super.addOptionsModification(consumer);
+  }
+
+  public R8PartialTestBuilder addR8PartialD8OptionsModification(
+      Consumer<InternalOptions> consumer) {
     return super.addOptionsModification(
         options ->
             options.partialCompilationConfiguration.d8DexOptionsConsumer =
                 options.partialCompilationConfiguration.d8DexOptionsConsumer.andThen(consumer));
   }
 
-  public R8PartialTestBuilder addR8PartialOptionsModification(Consumer<InternalOptions> consumer) {
+  public R8PartialTestBuilder addR8PartialR8OptionsModification(
+      Consumer<InternalOptions> consumer) {
     return super.addOptionsModification(
         options ->
             options.partialCompilationConfiguration.r8OptionsConsumer =
@@ -178,25 +176,39 @@
   }
 
   public R8PartialTestBuilder addGlobalOptionsModification(Consumer<InternalOptions> consumer) {
-    return addD8PartialOptionsModification(consumer)
-        .addR8PartialOptionsModification(consumer);
+    return addR8PartialD8OptionsModification(consumer).addR8PartialR8OptionsModification(consumer);
   }
 
   @Override
   public R8PartialTestBuilder allowUnnecessaryDontWarnWildcards() {
-    return addR8PartialOptionsModification(
+    return addR8PartialR8OptionsModification(
         options -> options.getTestingOptions().allowUnnecessaryDontWarnWildcards = true);
   }
 
   @Override
   public R8PartialTestBuilder allowUnusedDontWarnPatterns() {
-    return addR8PartialOptionsModification(
+    return addR8PartialR8OptionsModification(
         options -> options.getTestingOptions().allowUnusedDontWarnRules = true);
   }
 
   @Override
+  public R8PartialTestBuilder applyCompilerDump(CompilerDump dump) throws IOException {
+    List<String> includePatterns = dump.getR8PartialIncludePatterns();
+    if (includePatterns != null) {
+      List<String> excludePatterns =
+          dump.getR8PartialExcludePatternsOrDefault(Collections.emptyList());
+      setR8PartialConfiguration(
+          configuration -> {
+            includePatterns.forEach(configuration::addJavaTypeIncludePattern);
+            excludePatterns.forEach(configuration::addJavaTypeExcludePattern);
+          });
+    }
+    return super.applyCompilerDump(dump);
+  }
+
+  @Override
   public R8PartialTestBuilder enableExperimentalKeepAnnotations() {
-    return addR8PartialOptionsModification(
+    return addR8PartialR8OptionsModification(
             o -> o.getTestingOptions().enableEmbeddedKeepAnnotations = true)
         .addKeepAnnoLibToClasspath();
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
index ea40b20..294f719 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
@@ -96,6 +96,10 @@
     return self;
   }
 
+  public boolean isJvmTestBuilder() {
+    return false;
+  }
+
   @Deprecated
   public RR run(String mainClass)
       throws CompilationFailedException, ExecutionException, IOException {
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
index b017214..b5c886d 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -71,7 +71,6 @@
           options -> {
             options.testing.allowUnusedDontWarnRules = false;
             options.testing.allowUnnecessaryDontWarnWildcards = false;
-            options.testing.listIterationRewritingEnabled = true;
             options.horizontalClassMergerOptions().enable();
             options.horizontalClassMergerOptions().setEnableInterfaceMerging();
             options.inlinerOptions().enableConstructorInliningWithFinalFields = true;
diff --git a/src/test/testbase/java/com/android/tools/r8/TestParameters.java b/src/test/testbase/java/com/android/tools/r8/TestParameters.java
index ab3077c..ef797e2 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestParameters.java
@@ -287,6 +287,15 @@
     return (isDexRuntime() || representativeApiLevelForRuntime) && !isNoneRuntime();
   }
 
+  public TestParameters assumeR8PartialTestParameters() {
+    assumeTrue(isR8PartialTestParameters());
+    return this;
+  }
+
+  public boolean isR8PartialTestParameters() {
+    return isDexRuntime() && apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L);
+  }
+
   public TestParameters assumeRuntimeTestParameters() {
     assertFalse(
         "No need to use assumeRuntimeTestParameters() when not using api levels for CF",
diff --git a/src/test/testbase/java/com/android/tools/r8/desugar/AutoCloseableAndroidLibraryFileData.java b/src/test/testbase/java/com/android/tools/r8/desugar/AutoCloseableAndroidLibraryFileData.java
new file mode 100644
index 0000000..ec7aa63
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/AutoCloseableAndroidLibraryFileData.java
@@ -0,0 +1,240 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class AutoCloseableAndroidLibraryFileData extends TestBase {
+
+  public static Path compileAutoCloseableAndroidLibraryClasses(
+      TestBase testBase, TestParameters parameters) throws Exception {
+    return testBase
+        .testForD8()
+        .addProgramClassFileData(getAutoCloseableAndroidClassData(parameters))
+        .setMinApi(parameters)
+        .compile()
+        .writeToZip();
+  }
+
+  public static ImmutableList<byte[]> getAutoCloseableAndroidClassData(TestParameters parameters)
+      throws Exception {
+    return ImmutableList.of(
+        getMediaDrm(parameters),
+        getContentProviderClient(parameters),
+        getDrmManagerClient(parameters),
+        getMediaMetadataRetriever(parameters),
+        getTypedArray(parameters),
+        getTransformedBuildVERSIONClass());
+  }
+
+  private static byte[] getContentProviderClient(TestParameters parameters) throws IOException {
+    return getTransformedClass(
+        parameters,
+        AndroidApiLevel.N,
+        ContentProviderClientApiLevel24.class,
+        ContentProviderClient.class,
+        DexItemFactory.androidContentContentProviderClientDescriptorString);
+  }
+
+  private static byte[] getDrmManagerClient(TestParameters parameters) throws IOException {
+    return getTransformedClass(
+        parameters,
+        AndroidApiLevel.N,
+        DrmManagerClientApiLevel24.class,
+        DrmManagerClient.class,
+        DexItemFactory.androidDrmDrmManagerClientDescriptorString);
+  }
+
+  private static byte[] getMediaDrm(TestParameters parameters) throws IOException {
+    return getTransformedClass(
+        parameters,
+        AndroidApiLevel.P,
+        MediaDrmApiLevel28.class,
+        MediaDrm.class,
+        DexItemFactory.androidMediaMediaDrmDescriptorString);
+  }
+
+  private static byte[] getMediaMetadataRetriever(TestParameters parameters) throws IOException {
+    return getTransformedClass(
+        parameters,
+        AndroidApiLevel.Q,
+        MediaMetadataRetrieverApiLevel29.class,
+        MediaMetadataRetriever.class,
+        DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString);
+  }
+
+  private static byte[] getTypedArray(TestParameters parameters) throws IOException {
+    return getTransformedClass(
+        parameters,
+        AndroidApiLevel.S,
+        TypedArrayAndroidApiLevel31.class,
+        TypedArray.class,
+        DexItemFactory.androidContentResTypedArrayDescriptorString);
+  }
+
+  private static byte[] getTransformedClass(
+      TestParameters parameters,
+      AndroidApiLevel minApiLevel,
+      Class<?> lowApiClass,
+      Class<?> highApiClass,
+      String descriptorString)
+      throws IOException {
+    if (parameters.getApiLevel().isGreaterThanOrEqualTo(minApiLevel)) {
+      return transformer(lowApiClass).setClassDescriptor(descriptorString).clearNest().transform();
+    } else {
+      return transformer(highApiClass).setClassDescriptor(descriptorString).clearNest().transform();
+    }
+  }
+
+  private static byte[] getTransformedBuildVERSIONClass() throws IOException, NoSuchFieldException {
+    return transformer(VERSION.class)
+        .setClassDescriptor("Landroid/os/Build$VERSION;")
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+        .transform();
+  }
+
+  // Minimal android.os.Build$VERSION for runtime classpath.
+  public static class /*android.os.Build$*/ VERSION {
+
+    public static /*final*/ int SDK_INT = -1;
+  }
+
+  public static class ContentProviderClient {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      throw new AssertionError("close should not be called");
+    }
+
+    public boolean release() {
+      wasClosed = true;
+      return wasClosed;
+    }
+  }
+
+  public static class ContentProviderClientApiLevel24 implements AutoCloseable {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      wasClosed = true;
+    }
+
+    public boolean release() {
+      throw new AssertionError("release should not be called");
+    }
+  }
+
+  public static class DrmManagerClient {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      throw new AssertionError("close should not be called");
+    }
+
+    public void release() {
+      wasClosed = true;
+    }
+  }
+
+  public static class DrmManagerClientApiLevel24 implements AutoCloseable {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      wasClosed = true;
+    }
+
+    public void release() {
+      throw new AssertionError("release should not be called");
+    }
+  }
+
+  public static class MediaDrm {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      throw new AssertionError("close should not be called");
+    }
+
+    public void release() {
+      wasClosed = true;
+    }
+  }
+
+  public static class MediaDrmApiLevel28 implements AutoCloseable {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      wasClosed = true;
+    }
+
+    public void release() {
+      throw new AssertionError("release should not be called");
+    }
+  }
+
+  public static class MediaMetadataRetriever {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      throw new AssertionError("close should not be called");
+    }
+
+    public void release() {
+      wasClosed = true;
+    }
+  }
+
+  public static class MediaMetadataRetrieverApiLevel29 implements AutoCloseable {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      wasClosed = true;
+    }
+
+    public void release() {
+      throw new AssertionError("release should not be called");
+    }
+  }
+
+  public static class TypedArray {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      throw new AssertionError("close should not be called");
+    }
+
+    public void recycle() {
+      wasClosed = true;
+    }
+  }
+
+  public static class TypedArrayAndroidApiLevel31 implements AutoCloseable {
+
+    public boolean wasClosed = false;
+
+    public void close() {
+      wasClosed = true;
+    }
+
+    public void recycle() {
+      throw new AssertionError("recycle should not be called");
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 8eebd4a..cdbf02f 100644
--- a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -189,7 +189,7 @@
     ignoredInvokes.add(methodName);
   }
 
-  protected void configureProgram(TestBuilder<?, ?> builder) throws IOException {
+  protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
     builder.addProgramClasses(MiniAssert.class, IgnoreInvokes.class);
     if (testClass != null) {
       testClass.addAsProgramClass(builder);
@@ -198,6 +198,10 @@
     }
   }
 
+  protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+    // Intentionally empty.
+  }
+
   @Test
   public void testJvm() throws Exception {
     parameters.assumeJvmTestParameters();
@@ -233,6 +237,7 @@
     testForD8()
         .setMinApi(parameters)
         .apply(this::configureProgram)
+        .apply(this::configureD8Options)
         .setIncludeClassesChecksum(true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .apply(this::configure)
@@ -256,8 +261,10 @@
     testForD8(Backend.CF)
         .setMinApi(parameters)
         .apply(this::configureProgram)
+        .apply(this::configureD8Options)
         .setIncludeClassesChecksum(true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
+        .apply(this::configure)
         .run(parameters.getRuntime(), testClassName)
         .assertSuccess()
         .inspect(this::assertDesugaring);
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 2d63c57..8ed1bcc 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -262,6 +262,12 @@
     def config_file(self):
         return self.if_exists('proguard.config')
 
+    def r8_include_file(self):
+        return self.if_exists('r8-include.txt')
+
+    def r8_exclude_file(self):
+        return self.if_exists('r8-exclude.txt')
+
     def version_file(self):
         return self.if_exists('r8-version')
 
@@ -327,8 +333,10 @@
                     " No value for 'force-proguard-compatibility'.")
             if build_properties.get(
                     'force-proguard-compatibility').lower() == 'false':
-                compiler = compiler + 'full'
-        if compiler == 'TraceReferences':
+                compiler = 'r8full'
+        elif compiler == 'r8partial':
+            compiler = 'r8full'
+        elif compiler == 'TraceReferences':
             compiler = build_properties.get('tool').lower()
     if compiler not in compilers:
         error("Unable to determine a compiler to use. Specified %s,"
@@ -652,11 +660,20 @@
             cmd.append('com.android.tools.r8.tracereferences.TraceReferences')
             cmd.extend(
                 determine_trace_references_commands(build_properties, out))
-        if compiler.startswith('r8'):
+        if is_r8_compiler('r8'):
             prepare_r8_wrapper(jar, temp, jdkhome)
             cmd.append('com.android.tools.r8.utils.CompileDumpCompatR8')
-        if compiler == 'r8':
-            cmd.append('--compat')
+            if compiler == 'r8':
+                cmd.append('--compat')
+            elif compiler == 'r8full':
+                r8_partial_include_file = dump.r8_include_file()
+                if r8_partial_include_file:
+                    cmd.append('--partial-include')
+                    cmd.append(r8_partial_include_file)
+                    r8_partial_exclude_file = dump.r8_exclude_file()
+                    if r8_partial_exclude_file:
+                        cmd.append('--partial-exclude')
+                        cmd.append(r8_partial_exclude_file)
         if compiler != 'tracereferences':
             assert mode == 'debug' or mode == 'release'
             cmd.append('--' + mode)