AutoCloseable Retargeter

Bug: b/369520931
Change-Id: Ie6c56b338d9bfe51678f09c57e4f5cbba7b83859
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index 0728314..2b49823 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -31,9 +31,12 @@
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
 import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ThrowExceptionCode;
 import com.android.tools.r8.ir.conversion.PrimaryD8L8IRConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryTypeRewriter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordTagSynthesizer;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
@@ -74,6 +77,7 @@
     for (SyntheticKind kind : naming.kinds()) {
       assert !kind.isGlobal()
           || !kind.isMayOverridesNonProgramType()
+          || kind.equals(naming.AUTOCLOSEABLE_TAG)
           || kind.equals(naming.RECORD_TAG)
           || kind.equals(naming.API_MODEL_STUB)
           || kind.equals(naming.METHOD_HANDLES_LOOKUP)
@@ -180,6 +184,22 @@
     RecordTagSynthesizer.ensureRecordClassHelper(
         appView, synthesizingContext, RecordDesugaringEventConsumer.empty(), null, null);
 
+    AutoCloseableRetargeter.ensureAutoCloseableInterfaceTag(
+        synthesizingContext,
+        new AutoCloseableRetargeterEventConsumer() {
+          @Override
+          public void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag) {}
+
+          @Override
+          public void acceptAutoCloseableTagContext(
+              DexProgramClass autoCloseableTag, ProgramMethod context) {}
+
+          @Override
+          public void acceptAutoCloseableDispatchMethod(
+              ProgramMethod method, ProgramMethod context) {}
+        },
+        appView);
+
     VarHandleDesugaringEventConsumer varHandleEventConsumer =
         VarHandleDesugaringEventConsumer.empty();
 
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/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..ef35dce 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;
@@ -57,6 +58,7 @@
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.stream.StreamSupport;
 
 /**
  * Class that gets notified for structural changes made as a result of desugaring (e.g., the
@@ -77,7 +79,8 @@
         ApiInvokeOutlinerDesugaringEventConsumer,
         VarHandleDesugaringEventConsumer,
         DesugaredLibRewriterEventConsumer,
-        TypeSwitchDesugaringEventConsumer {
+        TypeSwitchDesugaringEventConsumer,
+        AutoCloseableRetargeterEventConsumer {
 
   public static CfInstructionDesugaringEventConsumer createForD8(
       AppView<?> appView,
@@ -259,6 +262,24 @@
     }
 
     @Override
+    public void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag) {
+      assert StreamSupport.stream(autoCloseableTag.programMethods().spliterator(), false)
+          .allMatch(m -> m.getDefinition().isAbstract());
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptAutoCloseableTagContext(
+        DexProgramClass autoCloseableTag, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramMethod context) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
+    @Override
     public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
       clazz
           .programMethods()
@@ -558,6 +579,22 @@
     }
 
     @Override
+    public void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag) {
+      // Intentionally empty. The class will be hit by tracing if required.
+    }
+
+    @Override
+    public void acceptAutoCloseableTagContext(
+        DexProgramClass autoCloseableTag, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramMethod 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/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/retargeter/AutoCloseableRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java
new file mode 100644
index 0000000..8756724
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.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.createCloseMethod;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
+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.DexEncodedMethod;
+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.ProgramDefinition;
+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 com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+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();
+    ensureAutoCloseableInterfaceTag(ImmutableList.of(context), eventConsumer, appView);
+    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(),
+                                        createCloseMethod(factory, factory.autoCloseableType),
+                                        createCloseMethod(factory, factory.autoCloseableTagType),
+                                        dispatchCases,
+                                        EmulateDispatchType.AUTO_CLOSEABLE,
+                                        appView)
+                                    .generateCfCode()));
+    eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+    return method.getReference();
+  }
+
+  public static DexType ensureAutoCloseableInterfaceTag(
+      Collection<? extends ProgramDefinition> contexts,
+      AutoCloseableRetargeterEventConsumer eventConsumer,
+      AppView<?> appView) {
+    return appView
+        .getSyntheticItems()
+        .ensureGlobalClass(
+            () -> new MissingGlobalSyntheticsConsumerDiagnostic("Autocloseable desugaring"),
+            kinds -> kinds.AUTOCLOSEABLE_TAG,
+            appView.dexItemFactory().autoCloseableTagType,
+            contexts,
+            appView,
+            builder ->
+                builder
+                    .setInterface()
+                    .setVirtualMethods(ImmutableList.of(synthesizeItfClose(appView))),
+            eventConsumer::acceptAutoCloseableTagProgramClass,
+            clazz -> {
+              for (ProgramDefinition context : contexts) {
+                if (context.isProgramMethod()) {
+                  eventConsumer.acceptAutoCloseableTagContext(clazz, context.asProgramMethod());
+                }
+              }
+            })
+        .getType();
+  }
+
+  private static DexEncodedMethod synthesizeItfClose(AppView<?> appView) {
+    MethodAccessFlags methodAccessFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT, false);
+    return DexEncodedMethod.syntheticBuilder()
+        .setMethod(
+            createCloseMethod(
+                appView.dexItemFactory(), appView.dexItemFactory().autoCloseableTagType))
+        .setAccessFlags(methodAccessFlags)
+        // Will be traced by the enqueuer.
+        .disableAndroidApiLevelCheck()
+        .build();
+  }
+
+  @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 dexClassAndMethod =
+          appView
+              .appInfoForDesugaring()
+              .lookupSuperTarget(singleTarget, context, appView, appView.appInfoForDesugaring());
+      if (dexClassAndMethod != null && dexClassAndMethod.isLibraryMethod()) {
+        DexType holderType = dexClassAndMethod.getHolderType();
+        if (data.superTargetsToRewrite().contains(holderType)) {
+          return createWithTarget(
+              (eventConsumer, methodProcessingContext) ->
+                  data.synthesizeDispatchCase(
+                      appView, holderType, context, eventConsumer, methodProcessingContext));
+        }
+      }
+      return DesugarDescription.nothing();
+    }
+    return createWithTarget(
+        (eventConsumer, methodProcessingContext) ->
+            synthesizeDispatcher(context, methodProcessingContext, eventConsumer));
+  }
+
+  private DesugarDescription createWithTarget(
+      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();
+              return Collections.singletonList(
+                  new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, false));
+            })
+        .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..0b591e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java
@@ -0,0 +1,17 @@
+// 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.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface AutoCloseableRetargeterEventConsumer {
+
+  void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag);
+
+  void acceptAutoCloseableTagContext(DexProgramClass autoCloseableTag, ProgramMethod context);
+
+  void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramMethod context);
+}
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..dad1e28
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java
@@ -0,0 +1,168 @@
+// 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.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
+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;
+
+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();
+    forEachAutoCloseableSubimplementation(
+        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.
+  private void forEachAutoCloseableSubimplementation(Consumer<DexType> consumer) {
+    if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.V)) {
+      consumer.accept(factory.javaUtilConcurrentExecutorServiceType);
+      consumer.accept(factory.javaUtilConcurrentForkJoinPoolType);
+    }
+    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);
+    }
+  }
+
+  // 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<>();
+    forEachAutoCloseableSubimplementation(
+        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));
+          }
+        });
+    return map;
+  }
+
+  public Set<DexType> superTargetsToRewrite() {
+    ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+    forEachAutoCloseableSubimplementation(builder::add);
+    return builder.build();
+  }
+
+  public DexMethod synthesizeDispatchCase(
+      AppView<?> appView,
+      DexType type,
+      ProgramMethod context,
+      AutoCloseableRetargeterEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    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, methodProcessingContext);
+    }
+    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,
+      ProgramMethod context,
+      AutoCloseableRetargeterEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.AUTOCLOSEABLE_DISPATCHER,
+                methodProcessingContext.createUniqueContext(),
+                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();
+  }
+}
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/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/synthetic/EmulateDispatchSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
index 9caa410..8df74a1 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,16 +47,21 @@
       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
   public CfCode generateCfCode() {
-    DexType receiverType = forwardingMethod.getParameter(0);
+    DexType receiverType =
+        dispatchType == AUTO_CLOSEABLE
+            ? forwardingMethod.getHolderType()
+            : forwardingMethod.getParameter(0);
     List<CfInstruction> instructions = new ArrayList<>();
     CfLabel[] labels = new CfLabel[extraDispatchCases.size() + 1];
     for (int i = 0; i < labels.length; i++) {
@@ -89,8 +103,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 +111,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 +142,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/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
index 3af5dc4..ee53f13 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,29 @@
   }
 
   @Override
+  public void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag) {
+    parent.acceptAutoCloseableTagProgramClass(autoCloseableTag);
+  }
+
+  @Override
+  public void acceptAutoCloseableTagContext(
+      DexProgramClass autoCloseableTag, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context,
+        additionsBuilder -> {
+          additionsBuilder.addRule(autoCloseableTag);
+          autoCloseableTag.forEachProgramMethod(additionsBuilder::addRule);
+        });
+    parent.acceptAutoCloseableTagContext(autoCloseableTag, context);
+  }
+
+  @Override
+  public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramMethod 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/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 0030cec..1cf3883 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -26,6 +26,7 @@
   private KindGenerator generator = new KindGenerator();
 
   // Global synthetics.
+  public final SyntheticKind AUTOCLOSEABLE_TAG = generator.forGlobalClass();
   public final SyntheticKind RECORD_TAG = generator.forGlobalClass();
   public final SyntheticKind API_MODEL_STUB = generator.forGlobalClass();
   public final SyntheticKind METHOD_HANDLES_LOOKUP = generator.forGlobalClass();
@@ -66,6 +67,8 @@
   public final SyntheticKind CONST_DYNAMIC = generator.forNonSharableInstanceClass("$Condy");
 
   // Method synthetics.
+  public final SyntheticKind AUTOCLOSEABLE_DISPATCHER =
+      generator.forSingleMethodWithGlobalMerging("AutoCloseableDispatcher");
   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/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index c54080b..b8ea329 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2122,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;
@@ -2698,6 +2699,13 @@
         && !canUseDefaultAndStaticInterfaceMethods();
   }
 
+  public boolean shouldDesugarAutoCloseable() {
+    return desugarState.isOn()
+        && getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)
+        && getMinApiLevel().isLessThanOrEqualTo(AndroidApiLevel.V)
+        && testing.enableAutoCloseableDesugaring;
+  }
+
   public boolean isSwitchRewritingEnabled() {
     return enableSwitchRewriting && !debug;
   }
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/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/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/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/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/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/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..e2a2276 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,6 +261,7 @@
     testForD8(Backend.CF)
         .setMinApi(parameters)
         .apply(this::configureProgram)
+        .apply(this::configureD8Options)
         .setIncludeClassesChecksum(true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), testClassName)