Merge commit 'f946160c75ad612b5f34af1dd5129e4799967d19' into dev-release

Change-Id: I421ef94f52515c9b4a2fd3f0ca702815e73c0946
diff --git a/src/blastradius/proto/blastradius.proto b/src/blastradius/proto/blastradius.proto
index ed3d8cd..e1b14da 100644
--- a/src/blastradius/proto/blastradius.proto
+++ b/src/blastradius/proto/blastradius.proto
@@ -42,6 +42,17 @@
   repeated uint32 method_blast_radius = 4;
 }
 
+// The ids of GlobalKeepRuleBlastRadius and KeepRuleBlastRadius are
+// non-overlapping by design, so that a single uint32 id can be used to
+// unambiguously reference a GlobalKeepRuleBlastRadius or KeepRuleBlastRadius.
+message GlobalKeepRuleBlastRadius {
+  uint32 id = 1;
+  // Textual representation of the keep rule.
+  string source = 2;
+  // Where this rule comes from.
+  TextFileOrigin origin = 3;
+}
+
 // Information about the constraints of a single keep annotation or rule,
 // i.e., what is prohibited by it.
 //
@@ -207,7 +218,8 @@
   // Keep specifications.
   repeated KeepConstraints keep_constraints_table = 12;
   repeated KeepRuleBlastRadius keep_rule_blast_radius_table = 13;
+  repeated GlobalKeepRuleBlastRadius global_keep_rule_blast_radius_table = 14;
 
   // Build info.
-  BuildInfo build_info = 14;
+  BuildInfo build_info = 15;
 }
diff --git a/src/blastradius/web/blastradius.proto b/src/blastradius/web/blastradius.proto
index ed3d8cd..e1b14da 100644
--- a/src/blastradius/web/blastradius.proto
+++ b/src/blastradius/web/blastradius.proto
@@ -42,6 +42,17 @@
   repeated uint32 method_blast_radius = 4;
 }
 
+// The ids of GlobalKeepRuleBlastRadius and KeepRuleBlastRadius are
+// non-overlapping by design, so that a single uint32 id can be used to
+// unambiguously reference a GlobalKeepRuleBlastRadius or KeepRuleBlastRadius.
+message GlobalKeepRuleBlastRadius {
+  uint32 id = 1;
+  // Textual representation of the keep rule.
+  string source = 2;
+  // Where this rule comes from.
+  TextFileOrigin origin = 3;
+}
+
 // Information about the constraints of a single keep annotation or rule,
 // i.e., what is prohibited by it.
 //
@@ -207,7 +218,8 @@
   // Keep specifications.
   repeated KeepConstraints keep_constraints_table = 12;
   repeated KeepRuleBlastRadius keep_rule_blast_radius_table = 13;
+  repeated GlobalKeepRuleBlastRadius global_keep_rule_blast_radius_table = 14;
 
   // Build info.
-  BuildInfo build_info = 14;
+  BuildInfo build_info = 15;
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3d0cf86..116af49 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -287,15 +287,14 @@
     timing.end();
     try {
       AppView<AppInfoWithClassHierarchy> appView;
-      List<KeepDeclaration> keepDeclarations;
       {
         timing.begin("Read app");
         ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
         DirectMappedDexApplication application = applicationReader.readDirect(executorService);
-        keepDeclarations =
-            options.partialSubCompilationConfiguration != null
-                ? options.partialSubCompilationConfiguration.asR8().getAndClearKeepDeclarations()
-                : application.getKeepDeclarations();
+        if (options.partialSubCompilationConfiguration != null) {
+          application.setKeepDeclarations(
+              options.partialSubCompilationConfiguration.asR8().getAndClearKeepDeclarations());
+        }
         timing.end();
         options
             .getLibraryDesugaringOptions()
@@ -316,6 +315,7 @@
       timing.begin("Register references and more setup");
       assert ArtProfileCompletenessChecker.verify(appView);
 
+      List<KeepDeclaration> keepDeclarations = appView.app().asDirect().getKeepDeclarations();
       readKeepSpecifications(appView, keepDeclarations);
 
       // Check for potentially having pass-through of Cf-code for kotlin libraries.
diff --git a/src/main/java/com/android/tools/r8/blastradius/RootSetBlastRadiusSerializer.java b/src/main/java/com/android/tools/r8/blastradius/RootSetBlastRadiusSerializer.java
index e826cae..06d0c2a 100644
--- a/src/main/java/com/android/tools/r8/blastradius/RootSetBlastRadiusSerializer.java
+++ b/src/main/java/com/android/tools/r8/blastradius/RootSetBlastRadiusSerializer.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.blastradius.proto.BuildInfo;
 import com.android.tools.r8.blastradius.proto.FieldReference;
 import com.android.tools.r8.blastradius.proto.FileOrigin;
+import com.android.tools.r8.blastradius.proto.GlobalKeepRuleBlastRadius;
 import com.android.tools.r8.blastradius.proto.KeepConstraint;
 import com.android.tools.r8.blastradius.proto.KeepConstraints;
 import com.android.tools.r8.blastradius.proto.KeepRuleBlastRadius;
@@ -30,9 +31,12 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.EnqueuerResult;
+import com.android.tools.r8.shaking.GlobalConfigurationRule;
+import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardKeepRuleBase;
 import com.android.tools.r8.shaking.ProguardKeepRuleModifiers;
 import com.android.tools.r8.utils.ArrayUtils;
@@ -101,6 +105,7 @@
               .setSource(blastRadiusForRule.getSource());
       container.addKeepRuleBlastRadiusTable(ruleProto);
     }
+    serializeGlobalKeepRuleBlastRadii(ruleIds.size());
     keptClassInfos.values().forEach(container::addKeptClassInfoTable);
     keptFieldInfos.values().forEach(container::addKeptFieldInfoTable);
     keptMethodInfos.values().forEach(container::addKeptMethodInfoTable);
@@ -226,6 +231,23 @@
         });
   }
 
+  private void serializeGlobalKeepRuleBlastRadii(int nextRuleId) {
+    ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
+    if (proguardConfiguration == null) {
+      return;
+    }
+    // The iteration over the global rules should have deterministic iteration order since the
+    // global rules lists are populated in-order by the ProguardConfigurationParser.
+    for (GlobalConfigurationRule rule : proguardConfiguration.getGlobalRules()) {
+      container.addGlobalKeepRuleBlastRadiusTable(
+          GlobalKeepRuleBlastRadius.newBuilder()
+              .setId(nextRuleId++)
+              .setOrigin(serializeTextFileOrigin(rule))
+              .setSource(rule.getSource())
+              .build());
+    }
+  }
+
   private MethodReference serializeMethodReference(DexMethod method) {
     return methodReferences.computeIfAbsent(
         method,
@@ -261,10 +283,18 @@
         });
   }
 
-  private TextFileOrigin serializeTextFileOrigin(ProguardKeepRuleBase keepRule) {
+  private TextFileOrigin serializeTextFileOrigin(GlobalConfigurationRule rule) {
+    return serializeTextFileOrigin(rule.getOrigin(), rule.getPosition());
+  }
+
+  private TextFileOrigin serializeTextFileOrigin(ProguardKeepRuleBase rule) {
+    return serializeTextFileOrigin(rule.getOrigin(), rule.getPosition());
+  }
+
+  private TextFileOrigin serializeTextFileOrigin(Origin origin, Position position) {
     int line, column;
-    if (keepRule.getPosition() instanceof TextRange) {
-      TextRange textRange = (TextRange) keepRule.getPosition();
+    if (position instanceof TextRange) {
+      TextRange textRange = (TextRange) position;
       line = textRange.getStart().getLine();
       column = textRange.getStart().getColumn();
     } else {
@@ -272,7 +302,7 @@
       column = -1;
     }
     return TextFileOrigin.newBuilder()
-        .setFileOriginId(serializeOrigin(keepRule.getOrigin()).getId())
+        .setFileOriginId(serializeOrigin(origin).getId())
         .setLineNumber(line)
         .setColumnNumber(column)
         .build();
@@ -325,6 +355,8 @@
   @SuppressWarnings("UnusedVariable")
   private boolean validate(BlastRadiusContainer container) {
     // TODO(b/441055269): Check that ids of ClassFileInJarOrigin and FileOrigin are non-overlapping.
+    // TODO(b/441055269): Check that ids of GlobalKeepRuleBlastRadius and KeepRuleBlastRadius are
+    //  non-overlapping.
     // TODO(b/441055269): Check that the reference constants pools do not contain duplicates.
     return true;
   }
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 e2ff939..ad2b61f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -8,16 +8,12 @@
 import com.android.tools.r8.ByteBufferProvider;
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.D8.ConvertedCfFiles;
-import com.android.tools.r8.DataDirectoryResource;
-import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DataResourceProvider;
-import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.SourceFileEnvironment;
 import com.android.tools.r8.debuginfo.DebugRepresentation;
 import com.android.tools.r8.debuginfo.DebugRepresentation.DebugRepresentationPredicate;
@@ -27,9 +23,7 @@
 import com.android.tools.r8.dex.distribution.FillFilesDistributor;
 import com.android.tools.r8.dex.distribution.MonoDexDistributor;
 import com.android.tools.r8.dex.jumbostrings.JumboStringRewriter;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer;
-import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -69,7 +63,6 @@
 import com.android.tools.r8.utils.OriginalSourceFiles;
 import com.android.tools.r8.utils.PredicateUtils;
 import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.SupplierUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.timing.Timing;
@@ -591,12 +584,8 @@
     if (dataResourceConsumer != null) {
       ImmutableList<DataResourceProvider> dataResourceProviders =
           appView.app().dataResourceProviders;
-      adaptAndPassDataResources(
-          options,
-          dataResourceConsumer,
-          dataResourceProviders,
-          resourceAdapter,
-          kotlinModuleSynthesizer);
+      DataResourceWriter.adaptAndPassDataResources(
+          options, dataResourceConsumer, dataResourceProviders, resourceAdapter);
 
       appView.appServices().write(appView, FeatureSplit.BASE, dataResourceConsumer);
       // Rewrite/synthesize kotlin_module files
@@ -608,8 +597,8 @@
     if (options.hasFeatureSplitConfiguration()) {
       for (DataResourceProvidersAndConsumer entry :
           options.getFeatureSplitConfiguration().getDataResourceProvidersAndConsumers()) {
-        adaptAndPassDataResources(
-            options, entry.consumer, entry.providers, resourceAdapter, kotlinModuleSynthesizer);
+        DataResourceWriter.adaptAndPassDataResources(
+            options, entry.consumer, entry.providers, resourceAdapter);
         appView.appServices().write(appView, entry.featureSplit, entry.consumer);
       }
     }
@@ -629,61 +618,6 @@
     }
   }
 
-  private static void adaptAndPassDataResources(
-      InternalOptions options,
-      DataResourceConsumer dataResourceConsumer,
-      Collection<DataResourceProvider> dataResourceProviders,
-      ResourceAdapter resourceAdapter,
-      KotlinModuleSynthesizer kotlinModuleSynthesizer) {
-    Set<String> generatedResourceNames = new HashSet<>();
-
-    for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
-      try {
-        dataResourceProvider.accept(
-            new Visitor() {
-              @Override
-              public void visit(DataDirectoryResource directory) {
-                DataDirectoryResource adapted = resourceAdapter.adaptIfNeeded(directory);
-                if (adapted != null) {
-                  dataResourceConsumer.accept(adapted, options.reporter);
-                  options.reporter.failIfPendingErrors();
-                }
-              }
-
-              @Override
-              public void visit(DataEntryResource file) {
-                if ("META-INF/MANIFEST.MF".equals(file.getName())) {
-                  // Many android library input .jar files contain a MANIFEST.MF. It does not make
-                  // sense to propagate them since they are manifests of the input libraries.
-                  if (options.isGeneratingDex()
-                      || options.getTestingOptions().forcePruneMetaInfManifestMf) {
-                    return;
-                  }
-                }
-                if (file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)) {
-                  // META-INF/services resources are handled separately.
-                  return;
-                }
-                if (kotlinModuleSynthesizer.isKotlinModuleFile(file)) {
-                  // .kotlin_module files are synthesized.
-                  return;
-                }
-                DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
-                if (generatedResourceNames.add(adapted.getName())) {
-                  dataResourceConsumer.accept(adapted, options.reporter);
-                } else {
-                  options.reporter.warning(
-                      new StringDiagnostic("Resource '" + file.getName() + "' already exists."));
-                }
-                options.reporter.failIfPendingErrors();
-              }
-            });
-      } catch (ResourceException e) {
-        throw new CompilationError(e.getMessage(), e);
-      }
-    }
-  }
-
   private void insertAttributeAnnotations() {
     // Convert inner-class attributes to DEX annotations
     for (DexProgramClass clazz : appView.appInfo().classes()) {
diff --git a/src/main/java/com/android/tools/r8/dex/DataResourceWriter.java b/src/main/java/com/android/tools/r8/dex/DataResourceWriter.java
new file mode 100644
index 0000000..f0f79b1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/DataResourceWriter.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2026, 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.dex;
+
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.naming.KotlinModuleSynthesizer;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class DataResourceWriter {
+
+  public static void adaptAndPassDataResources(
+      InternalOptions options,
+      DataResourceConsumer dataResourceConsumer,
+      Collection<DataResourceProvider> dataResourceProviders,
+      ResourceAdapter resourceAdapter) {
+    Set<String> generatedResourceNames = new HashSet<>();
+
+    for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
+      try {
+        dataResourceProvider.accept(
+            new Visitor() {
+              @Override
+              public void visit(DataDirectoryResource directory) {
+                if (shouldDropDataDirectoryResource(directory, options)) {
+                  return;
+                }
+                DataDirectoryResource adapted = resourceAdapter.adaptIfNeeded(directory);
+                if (adapted != null) {
+                  dataResourceConsumer.accept(adapted, options.reporter);
+                  options.reporter.failIfPendingErrors();
+                }
+              }
+
+              @Override
+              public void visit(DataEntryResource file) {
+                if (shouldDropDataEntryResource(file, options)) {
+                  return;
+                }
+                DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
+                if (generatedResourceNames.add(adapted.getName())) {
+                  dataResourceConsumer.accept(adapted, options.reporter);
+                } else {
+                  options.reporter.warning(
+                      new StringDiagnostic("Resource '" + file.getName() + "' already exists."));
+                }
+                options.reporter.failIfPendingErrors();
+              }
+            });
+      } catch (ResourceException e) {
+        throw new CompilationError(e.getMessage(), e);
+      }
+    }
+  }
+
+  public static boolean shouldDropDataDirectoryResource(
+      DataDirectoryResource directory, InternalOptions options) {
+    if (options.getProguardConfiguration() == null) {
+      assert options.testing.enableD8MetaInfServicesPassThrough;
+      return true;
+    }
+    return !options.getProguardConfiguration().getKeepDirectories().matches(directory.getName());
+  }
+
+  public static boolean shouldDropDataEntryResource(
+      DataEntryResource file, InternalOptions options) {
+    if ("META-INF/MANIFEST.MF".equals(file.getName())) {
+      // Many android library input .jar files contain a MANIFEST.MF. It does not make
+      // sense to propagate them since they are manifests of the input libraries.
+      if (options.isGeneratingDex() || options.getTestingOptions().forcePruneMetaInfManifestMf) {
+        return true;
+      }
+    }
+    if (file.getName().startsWith(AppServices.SERVICE_DIRECTORY_NAME)) {
+      // META-INF/services resources are handled separately.
+      return true;
+    }
+    if (KotlinModuleSynthesizer.isKotlinModuleFile(file)) {
+      // .kotlin_module files are synthesized.
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index e9b35f4..ddb9418 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.dex;
 
+import static com.android.tools.r8.graph.lens.GraphLens.getIdentityLens;
+
 import com.android.tools.r8.DataDirectoryResource;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.ResourceException;
@@ -46,12 +48,15 @@
     this.options = appView.options();
   }
 
+  public String adaptFileNameIfNeeded(DataEntryResource file) {
+    return shouldAdapt(file, options, ProguardConfiguration::getAdaptResourceFilenames)
+        ? adaptFileName(file)
+        : file.getName();
+  }
+
   public DataEntryResource adaptIfNeeded(DataEntryResource file) {
     // Adapt name, if needed.
-    String name =
-        shouldAdapt(file, options, ProguardConfiguration::getAdaptResourceFilenames)
-            ? adaptFileName(file)
-            : file.getName();
+    String name = adaptFileNameIfNeeded(file);
     assert name != null;
     // Adapt contents, if needed.
     byte[] contents =
@@ -74,14 +79,6 @@
   }
 
   public DataDirectoryResource adaptIfNeeded(DataDirectoryResource directory) {
-    // First check if this directory should even be in the output.
-    if (options.getProguardConfiguration() == null) {
-      assert options.testing.enableD8MetaInfServicesPassThrough;
-      return null;
-    }
-    if (!options.getProguardConfiguration().getKeepDirectories().matches(directory.getName())) {
-      return null;
-    }
     return DataDirectoryResource.fromName(adaptDirectoryName(directory), directory.getOrigin());
   }
 
@@ -111,12 +108,21 @@
     return file.getName();
   }
 
-  private String adaptDirectoryName(DataDirectoryResource file) {
-    DirectoryNameAdapter adapter = new DirectoryNameAdapter(file.getName());
+  public String adaptDirectoryName(DataDirectoryResource directory) {
+    DirectoryNameAdapter adapter = new DirectoryNameAdapter(directory.getName());
     if (adapter.run()) {
       return adapter.getResult();
     }
-    return file.getName();
+    return directory.getName();
+  }
+
+  protected String adaptPackage(String javaPackage) {
+    String packageName = appView.graphLens().lookupPackageName(javaPackage);
+    return namingLens.lookupPackageName(packageName);
+  }
+
+  private DexString adaptType(DexType type) {
+    return namingLens.lookupDescriptor(graphLens.lookupType(type, getIdentityLens()));
   }
 
   // According to the Proguard documentation, the resource files should be parsed and written using
@@ -271,7 +277,7 @@
               DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(javaType));
       DexType dexType = descriptor != null ? dexItemFactory.lookupType(descriptor) : null;
       if (dexType != null) {
-        DexString renamedDescriptor = namingLens.lookupDescriptor(graphLens.lookupType(dexType));
+        DexString renamedDescriptor = adaptType(dexType);
         if (!descriptor.equals(renamedDescriptor)) {
           String renamedJavaType =
               DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString());
@@ -296,8 +302,7 @@
       if (getClassNameSeparator() != '/') {
         javaPackage = javaPackage.replace(getClassNameSeparator(), '/');
       }
-      String packageName = appView.graphLens().lookupPackageName(javaPackage);
-      String minifiedJavaPackage = namingLens.lookupPackageName(packageName);
+      String minifiedJavaPackage = adaptPackage(javaPackage);
       if (!javaPackage.equals(minifiedJavaPackage)) {
         outputRangeFromInput(outputFrom, from);
         outputJavaType(
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 5002ee7..ff0aca1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -356,6 +356,7 @@
 
   public final DexString newUpdaterName = createString("newUpdater");
   public final DexString compareAndSetName = createString("compareAndSet");
+  public final DexString getName = createString("get");
 
   public final DexString constructorMethodName = createString(Constants.INSTANCE_INITIALIZER_NAME);
   public final DexString classConstructorMethodName =
@@ -2589,6 +2590,7 @@
     public final DexMethod longUpdater;
     public final DexMethod referenceUpdater;
     public final DexMethod referenceCompareAndSet;
+    public final DexMethod referenceGet;
     private final Set<DexMethod> updaters;
 
     private AtomicFieldUpdaterMethods() {
@@ -2610,13 +2612,19 @@
               newUpdaterName,
               referenceFieldUpdaterDescriptor,
               new DexString[] {classDescriptor, classDescriptor, stringDescriptor});
+      updaters = ImmutableSet.of(intUpdater, longUpdater, referenceUpdater);
       referenceCompareAndSet =
           createMethod(
               referenceFieldUpdaterDescriptor,
               compareAndSetName,
               booleanDescriptor,
               new DexString[] {objectDescriptor, objectDescriptor, objectDescriptor});
-      updaters = ImmutableSet.of(intUpdater, longUpdater, referenceUpdater);
+      referenceGet =
+          createMethod(
+              referenceFieldUpdaterDescriptor,
+              getName,
+              objectDescriptor,
+              new DexString[] {objectDescriptor});
     }
 
     public boolean isFieldUpdater(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 30c2029..a8ded38 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -90,6 +90,10 @@
     return keepDeclarations;
   }
 
+  public void setKeepDeclarations(List<KeepDeclaration> keepDeclarations) {
+    this.keepDeclarations = keepDeclarations;
+  }
+
   public Collection<DexLibraryClass> libraryClasses() {
     return libraryClasses.values();
   }
@@ -239,6 +243,7 @@
       super(application);
       classpathClasses = application.classpathClasses;
       libraryClasses = application.libraryClasses;
+      keepDeclarations = application.keepDeclarations;
     }
 
     private Builder(InternalOptions options, Timing timing) {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
index 272f1e9..483ab9c 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Iterator;
 import java.util.Set;
@@ -65,7 +64,11 @@
   }
 
   public Set<DexProgramClass> classesInPackage() {
-    return ImmutableSet.copyOf(classes);
+    return classes;
+  }
+
+  public int size() {
+    return classes.size();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
index 889d772..7a5daf8 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
@@ -44,6 +44,10 @@
     return packages.get(clazz.getType().getPackageDescriptor());
   }
 
+  public ProgramPackage getPackage(String packageDescriptor) {
+    return packages.get(packageDescriptor);
+  }
+
   public boolean isEmpty() {
     return packages.isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/AtomicFieldUpdaterOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/AtomicFieldUpdaterOptimizer.java
index ba0671e..1cbc19f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/AtomicFieldUpdaterOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/AtomicFieldUpdaterOptimizer.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IRCode;
@@ -21,6 +20,7 @@
 import com.android.tools.r8.ir.optimize.AtomicFieldUpdaterInstrumentor.AtomicFieldUpdaterInstrumentorInfo;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
+import java.util.Map;
 
 /**
  * This pass uses the information and instrumentation of {@code AtomicFieldUpdaterInstrumentor} to
@@ -82,7 +82,6 @@
   @Override
   protected CodeRewriterResult rewriteCode(IRCode code) {
     AtomicFieldUpdaterInstrumentorInfo info = appView.getAtomicFieldUpdaterInstrumentorInfo();
-    DexItemFactory factory = appView.dexItemFactory();
     var atomicUpdaterFields = info.getInstrumentations().get(code.context().getHolderType());
     assert atomicUpdaterFields != null;
 
@@ -104,90 +103,197 @@
       }
       var invoke = next.asInvokeVirtual();
       assert invoke != null;
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      if (!invokedMethod.holder.isIdenticalTo(
+          dexItemFactory.javaUtilConcurrentAtomicAtomicReferenceFieldUpdater)) {
+        continue;
+      }
 
       // Check for updater.compareAndSet(holder, expect, update) call.
-      if (!invoke
-          .getInvokedMethod()
-          .isIdenticalTo(factory.atomicFieldUpdaterMethods.referenceCompareAndSet)) {
-        continue;
-      }
-
-      // Resolve updater.
-      var updaterValue = invoke.getReceiver();
-      var updaterMightBeNull = updaterValue.getType().isNullable();
-      DexField updaterField;
-      var updaterAbstractValue =
-          updaterValue.getAbstractValue(appView, code.context()).removeNullOrAbstractValue();
-      if (updaterAbstractValue.isSingleFieldValue()) {
-        updaterField = updaterAbstractValue.asSingleFieldValue().getField();
+      if (invokedMethod.isIdenticalTo(
+          dexItemFactory.atomicFieldUpdaterMethods.referenceCompareAndSet)) {
+        if (visitCompareAndSet(
+            code,
+            it,
+            next.getPosition(),
+            invoke,
+            atomicUpdaterFields,
+            info.getUnsafeInstanceField(),
+            next.outValue())) {
+          changed = true;
+        }
+      } else if (invokedMethod.isIdenticalTo(
+          dexItemFactory.atomicFieldUpdaterMethods.referenceGet)) {
+        if (visitGet(
+            code,
+            it,
+            next.getPosition(),
+            invoke,
+            atomicUpdaterFields,
+            info.getUnsafeInstanceField(),
+            next.outValue())) {
+          changed = true;
+        }
       } else {
         reportFailure(
-            next.getPosition(),
-            "HERE.compareAndSet(..) is statically unclear or unhelpful: " + updaterAbstractValue);
-        continue;
+            next.getPosition(), "not implemented: " + invokedMethod.name.toSourceString());
       }
-      var updaterInfo = atomicUpdaterFields.get(updaterField);
-      if (updaterInfo == null) {
-        reportFailure(
-            next.getPosition(),
-            "HERE.compareAndSet(..) refers to an un-instrumented updater field");
-        continue;
-      }
-
-      // Resolve holder.
-      var holderValue = invoke.getFirstNonReceiverArgument();
-      var expectedHolder = updaterInfo.holder;
-      if (!holderValue
-          .getType()
-          .lessThanOrEqual(expectedHolder.toNonNullTypeElement(appView), appView)) {
-        if (appView.testing().enableAtomicFieldUpdaterLogs) {
-          if (holderValue
-              .getType()
-              .lessThanOrEqual(expectedHolder.toTypeElement(appView), appView)) {
-            reportFailure(next.getPosition(), "_.compareAndSet(HERE, _, _) is nullable");
-          } else {
-            reportFailure(next.getPosition(), "_.compareAndSet(HERE, _, _) is of unexpected type");
-          }
-        }
-        continue;
-      }
-
-      // Resolve expect.
-      var expectValue = invoke.getSecondNonReceiverArgument();
-
-      // Resolve update.
-      var updateValue = invoke.getThirdNonReceiverArgument();
-      var expectedType = updaterInfo.reflectedFieldType;
-      if (!updateValue.getType().lessThanOrEqual(expectedType.toTypeElement(appView), appView)) {
-        reportFailure(next.getPosition(), "_.compareAndSet(_, _, HERE) is of unexpected type");
-        continue;
-      }
-
-      reportSuccess(next.getPosition(), updaterMightBeNull);
-      rewriteToOptimizedCall(
-          code,
-          it,
-          next.getPosition(),
-          updaterMightBeNull,
-          info.getUnsafeInstanceField(),
-          updaterValue,
-          holderValue,
-          updaterInfo.offsetField,
-          expectValue,
-          updateValue,
-          next.outValue());
-      changed = true;
     }
     return CodeRewriterResult.hasChanged(changed);
   }
 
+  private boolean visitCompareAndSet(
+      IRCode code,
+      IRCodeInstructionListIterator it,
+      Position position,
+      InvokeVirtual invoke,
+      Map<DexField, AtomicFieldUpdaterInfo> atomicUpdaterFields,
+      DexField unsafeInstanceField,
+      Value outValue) {
+    // Resolve updater.
+    var updaterValue = invoke.getReceiver();
+    var resolvedUpdater =
+        resolveUpdater(code, position, atomicUpdaterFields, updaterValue, "compareAndSet");
+    if (resolvedUpdater == null) {
+      return false;
+    }
+
+    // Resolve holder.
+    var holderValue = invoke.getFirstNonReceiverArgument();
+    var expectedHolder = resolvedUpdater.updaterFieldInfo.holder;
+    if (!isHolderValid(position, holderValue, expectedHolder, "compareAndSet")) {
+      return false;
+    }
+
+    // Resolve expect.
+    var expectValue = invoke.getSecondNonReceiverArgument();
+
+    // Resolve update.
+    var updateValue = invoke.getThirdNonReceiverArgument();
+    var expectedType = resolvedUpdater.updaterFieldInfo.reflectedFieldType;
+    if (!updateValue.getType().lessThanOrEqual(expectedType.toTypeElement(appView), appView)) {
+      reportFailure(position, "_.compareAndSet(_, _, HERE) is of unexpected type");
+      return false;
+    }
+
+    reportSuccess(position, resolvedUpdater.isNullable);
+    rewriteCompareAndSet(
+        code,
+        it,
+        position,
+        resolvedUpdater.isNullable,
+        unsafeInstanceField,
+        updaterValue,
+        holderValue,
+        resolvedUpdater.updaterFieldInfo.offsetField,
+        expectValue,
+        updateValue,
+        outValue);
+    return true;
+  }
+
+  private boolean visitGet(
+      IRCode code,
+      IRCodeInstructionListIterator it,
+      Position position,
+      InvokeVirtual invoke,
+      Map<DexField, AtomicFieldUpdaterInfo> atomicUpdaterFields,
+      DexField unsafeInstanceField,
+      Value outValue) {
+    // Resolve updater.
+    var updaterValue = invoke.getReceiver();
+    var resolvedUpdater = resolveUpdater(code, position, atomicUpdaterFields, updaterValue, "get");
+    if (resolvedUpdater == null) {
+      return false;
+    }
+
+    // Resolve holder.
+    var holderValue = invoke.getFirstNonReceiverArgument();
+    var expectedHolder = resolvedUpdater.updaterFieldInfo.holder;
+    if (!isHolderValid(position, holderValue, expectedHolder, "get")) {
+      return false;
+    }
+
+    reportSuccess(position, resolvedUpdater.isNullable);
+    rewriteGet(
+        code,
+        it,
+        position,
+        resolvedUpdater.isNullable,
+        unsafeInstanceField,
+        updaterValue,
+        holderValue,
+        resolvedUpdater.updaterFieldInfo.offsetField,
+        outValue);
+    return true;
+  }
+
+  private ResolvedUpdater resolveUpdater(
+      IRCode code,
+      Position position,
+      Map<DexField, AtomicFieldUpdaterInfo> atomicUpdaterFields,
+      Value updaterValue,
+      String methodNameForLogging) {
+    var updaterMightBeNull = updaterValue.getType().isNullable();
+    DexField updaterField;
+    var updaterAbstractValue =
+        updaterValue.getAbstractValue(appView, code.context()).removeNullOrAbstractValue();
+    if (updaterAbstractValue.isSingleFieldValue()) {
+      updaterField = updaterAbstractValue.asSingleFieldValue().getField();
+    } else {
+      reportFailure(
+          position,
+          "HERE."
+              + methodNameForLogging
+              + "(..) is statically unclear or unhelpful: "
+              + updaterAbstractValue);
+      return null;
+    }
+    var updaterInfo = atomicUpdaterFields.get(updaterField);
+    if (updaterInfo == null) {
+      reportFailure(
+          position,
+          "HERE." + methodNameForLogging + "(..) refers to an un-instrumented updater field");
+      return null;
+    }
+    return new ResolvedUpdater(updaterMightBeNull, updaterInfo);
+  }
+
+  private static class ResolvedUpdater {
+
+    final boolean isNullable;
+    final AtomicFieldUpdaterInfo updaterFieldInfo;
+
+    private ResolvedUpdater(boolean isNullable, AtomicFieldUpdaterInfo updaterFieldInfo) {
+      this.isNullable = isNullable;
+      this.updaterFieldInfo = updaterFieldInfo;
+    }
+  }
+
+  private boolean isHolderValid(
+      Position position, Value holderValue, DexType expectedHolder, String methodNameForLogging) {
+    if (holderValue
+        .getType()
+        .lessThanOrEqual(expectedHolder.toNonNullTypeElement(appView), appView)) {
+      return true;
+    }
+    if (appView.testing().enableAtomicFieldUpdaterLogs) {
+      if (holderValue.getType().lessThanOrEqual(expectedHolder.toTypeElement(appView), appView)) {
+        reportFailure(position, "_." + methodNameForLogging + "(HERE, ..) is nullable");
+      } else {
+        reportFailure(position, "_." + methodNameForLogging + "(HERE, ..) is of unexpected type");
+      }
+    }
+    return false;
+  }
+
   /**
    * Rewrites a call to {@code updater.compareAndSet(holder, expect, update)} (assumed to be the
    * last instruction returned by {@code it.next}) into a call {@code
    * SyntheticUnsafeClass.unsafe.compareAndSwapObject(holder, this.offsetField, expect, update)} and
    * potentially a null-check on updater.
    */
-  private void rewriteToOptimizedCall(
+  private void rewriteCompareAndSet(
       IRCode code,
       IRCodeInstructionListIterator it,
       Position position,
@@ -199,51 +305,31 @@
       Value expectValue,
       Value updateValue,
       Value outValue) {
-    var factory = appView.dexItemFactory();
     var instructions = new ArrayList<Instruction>(3);
 
-    // Null-check for updater.
     if (updaterMightBeNull) {
-      var nullCheck =
-          new InvokeVirtual(
-              factory.objectMembers.getClass,
-              code.createValue(factory.classType.toTypeElement(appView)),
-              ImmutableList.of(updaterValue));
-      nullCheck.setPosition(position);
-      instructions.add(nullCheck);
+      instructions.add(createNullCheck(code, position, updaterValue));
     }
 
-    // Get unsafe instance.
-    assert unsafeInstanceField.type.isIdenticalTo(factory.unsafeType);
-    Instruction unsafeInstance =
-        new StaticGet(
-            code.createValue(factory.unsafeType.toTypeElement(appView)), unsafeInstanceField);
-    unsafeInstance.setPosition(position);
+    Instruction unsafeInstance = createUnsafeGet(code, position, unsafeInstanceField);
     instructions.add(unsafeInstance);
 
-    // Get offset field.
-    assert offsetField.type.isIdenticalTo(factory.longType);
-    Instruction offset =
-        new StaticGet(code.createValue(factory.longType.toTypeElement(appView)), offsetField);
-    offset.setPosition(position);
+    Instruction offset = createOffsetGet(code, position, offsetField);
     instructions.add(offset);
 
     // Add instructions BEFORE the compareAndSet instruction.
-    it.previous();
-    // TODO(b/453628974): Test with a local exception handler.
-    it.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(instructions, appView.options());
-    it.next();
+    insertInstructionsBeforeCurrentInstruction(it, instructions);
 
     // Call underlying unsafe method.
     DexMethod unsafeCompareAndSetMethod =
-        factory.createMethod(
-            factory.unsafeType,
-            factory.createProto(
-                factory.booleanType,
-                factory.objectType,
-                factory.longType,
-                factory.objectType,
-                factory.objectType),
+        dexItemFactory.createMethod(
+            dexItemFactory.unsafeType,
+            dexItemFactory.createProto(
+                dexItemFactory.booleanType,
+                dexItemFactory.objectType,
+                dexItemFactory.longType,
+                dexItemFactory.objectType,
+                dexItemFactory.objectType),
             "compareAndSwapObject");
     Instruction unsafeCompareAndSet =
         new InvokeVirtual(
@@ -260,6 +346,94 @@
     // TODO(b/453628974): Does profiling need to be updated?
   }
 
+  /**
+   * Rewrites a call to {@code updater.get(holder)} (assumed to be the last instruction returned by
+   * {@code it.next}) into a call {@code SyntheticUnsafeClass.unsafe.getReferenceVolatile(holder)}
+   * and potentially a null-check on updater.
+   */
+  private void rewriteGet(
+      IRCode code,
+      IRCodeInstructionListIterator it,
+      Position position,
+      boolean updaterMightBeNull,
+      DexField unsafeInstanceField,
+      Value updaterValue,
+      Value holderValue,
+      DexField offsetField,
+      Value outValue) {
+    var instructions = new ArrayList<Instruction>(3);
+
+    // Null-check for updater.
+    if (updaterMightBeNull) {
+      instructions.add(createNullCheck(code, position, updaterValue));
+    }
+
+    // Get unsafe instance.
+    Instruction unsafeInstance = createUnsafeGet(code, position, unsafeInstanceField);
+    instructions.add(unsafeInstance);
+
+    // Get offset field.
+    Instruction offset = createOffsetGet(code, position, offsetField);
+    instructions.add(offset);
+
+    // Add instructions BEFORE the get instruction.
+    insertInstructionsBeforeCurrentInstruction(it, instructions);
+
+    // Call underlying unsafe method.
+    DexMethod unsafeGetMethod =
+        dexItemFactory.createMethod(
+            dexItemFactory.unsafeType,
+            dexItemFactory.createProto(
+                dexItemFactory.objectType, dexItemFactory.objectType, dexItemFactory.longType),
+            "getObjectVolatile");
+    Instruction unsafeGet =
+        new InvokeVirtual(
+            unsafeGetMethod,
+            outValue,
+            ImmutableList.of(unsafeInstance.outValue(), holderValue, offset.outValue()));
+    unsafeGet.setPosition(position);
+    it.replaceCurrentInstruction(unsafeGet);
+    // TODO(b/453628974): Does profiling need to be updated?
+  }
+
+  private Instruction createOffsetGet(IRCode code, Position position, DexField offsetField) {
+    assert offsetField.type.isIdenticalTo(dexItemFactory.longType);
+    Instruction offset =
+        new StaticGet(
+            code.createValue(dexItemFactory.longType.toTypeElement(appView)), offsetField);
+    offset.setPosition(position);
+    return offset;
+  }
+
+  private InvokeVirtual createNullCheck(IRCode code, Position position, Value updaterValue) {
+    var nullCheck =
+        new InvokeVirtual(
+            dexItemFactory.objectMembers.getClass,
+            code.createValue(dexItemFactory.classType.toTypeElement(appView)),
+            ImmutableList.of(updaterValue));
+    nullCheck.setPosition(position);
+    return nullCheck;
+  }
+
+  private Instruction createUnsafeGet(
+      IRCode code, Position position, DexField unsafeInstanceField) {
+    assert unsafeInstanceField.type.isIdenticalTo(dexItemFactory.unsafeType);
+    Instruction unsafeInstance =
+        new StaticGet(
+            code.createValue(dexItemFactory.unsafeType.toTypeElement(appView)),
+            unsafeInstanceField);
+    unsafeInstance.setPosition(position);
+    return unsafeInstance;
+  }
+
+  private void insertInstructionsBeforeCurrentInstruction(
+      IRCodeInstructionListIterator it, ArrayList<Instruction> instructions) {
+    it.previous();
+    // TODO(b/453628974): Test with a local exception handler.
+    it.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(instructions, appView.options());
+    it.next();
+  }
+
   private void reportFailure(Position position, String reason) {
     if (!appView.testing().enableAtomicFieldUpdaterLogs) {
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java b/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java
index cfd20f4..db012d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java
@@ -330,30 +330,37 @@
       return null;
     }
     var fieldNameIns = fieldNameValue.definition;
-    // TODO(b/453628974): Add test with an invalid field name (this is then a const string
-    // instruction).
-    if (!fieldNameIns.isDexItemBasedConstString()) {
-      logs.reportFailure(updaterField, "newUpdater(_, _, HERE) is not a dex-item-based string");
+    ProgramField reflectedField;
+    if (fieldNameIns.isDexItemBasedConstString()) {
+      var fieldNameReference = fieldNameIns.asDexItemBasedConstString().getItem();
+      if (!fieldNameReference.isDexField()) {
+        logs.reportFailure(
+            updaterField, "newUpdater(_, _, HERE) is a dex reference to a non-field");
+        return null;
+      }
+      var reflectedFieldReference = fieldNameReference.asDexField();
+      if (!reflectedFieldReference.getHolderType().isIdenticalTo(clazz.getType())) {
+        logs.reportFailure(
+            updaterField, "newUpdater(_, _, HERE) is a dex reference to a field of another class.");
+        return null;
+      }
+      if (!reflectedFieldReference.type.isIdenticalTo(fieldType)) {
+        logs.reportFailure(
+            updaterField, "newUpdater(_, TYPE, FIELD) FIELD's type and TYPE disagree");
+        return null;
+      }
+      reflectedField = clazz.lookupProgramField(reflectedFieldReference);
+    } else if (fieldNameIns.isConstString()) {
+      var fieldNameString = fieldNameIns.asConstString().getValue();
+      reflectedField =
+          clazz.lookupProgramField(
+              itemFactory.createField(clazz.getType(), fieldType, fieldNameString));
+    } else {
+      logs.reportFailure(updaterField, "newUpdater(_, _, HERE) is not a string constant");
       return null;
     }
-    var fieldNameReference = fieldNameIns.asDexItemBasedConstString().getItem();
-    if (!fieldNameReference.isDexField()) {
-      logs.reportFailure(updaterField, "newUpdater(_, _, HERE) is a dex reference to a non-field");
-      return null;
-    }
-    var reflectedFieldReference = fieldNameReference.asDexField();
-    if (!reflectedFieldReference.getHolderType().isIdenticalTo(clazz.getType())) {
-      logs.reportFailure(
-          updaterField, "newUpdater(_, _, HERE) is a dex reference to a field of another class.");
-      return null;
-    }
-    if (!reflectedFieldReference.type.isIdenticalTo(fieldType)) {
-      logs.reportFailure(updaterField, "newUpdater(_, TYPE, FIELD) FIELD's type and TYPE disagree");
-      return null;
-    }
-    var reflectedField = clazz.lookupProgramField(reflectedFieldReference);
     if (reflectedField == null) {
-      logs.reportFailure(updaterField, "newUpdater(..) does not validly refer to a field");
+      logs.reportFailure(updaterField, "newUpdater(..) does not refer to a field");
       return null;
     }
     if (!reflectedField.getAccessFlags().isVolatile()) {
diff --git a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
index 96bd80a..e5daf62 100644
--- a/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
+++ b/src/main/java/com/android/tools/r8/metadata/R8BuildMetadata.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.metadata.impl.R8DexFileMetadataImpl;
 import com.android.tools.r8.metadata.impl.R8FeatureSplitMetadataImpl;
 import com.android.tools.r8.metadata.impl.R8FeatureSplitsMetadataImpl;
+import com.android.tools.r8.metadata.impl.R8KeepAnnotationsMetadataImpl;
 import com.android.tools.r8.metadata.impl.R8KeepAttributesMetadataImpl;
 import com.android.tools.r8.metadata.impl.R8LibraryDesugaringMetadataImpl;
 import com.android.tools.r8.metadata.impl.R8OptionsMetadataImpl;
@@ -46,6 +47,8 @@
             .registerTypeAdapter(
                 R8FeatureSplitsMetadata.class, deserializeTo(R8FeatureSplitsMetadataImpl.class))
             .registerTypeAdapter(
+                R8KeepAnnotationsMetadata.class, deserializeTo(R8KeepAnnotationsMetadataImpl.class))
+            .registerTypeAdapter(
                 R8KeepAttributesMetadata.class, deserializeTo(R8KeepAttributesMetadataImpl.class))
             .registerTypeAdapter(
                 R8LibraryDesugaringMetadata.class,
diff --git a/src/main/java/com/android/tools/r8/metadata/R8KeepAnnotationsMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8KeepAnnotationsMetadata.java
new file mode 100644
index 0000000..b04d291
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/R8KeepAnnotationsMetadata.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2026, 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.metadata;
+
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+
+@KeepForApi
+public interface R8KeepAnnotationsMetadata {
+
+  int getNumberOfKeepAnnotations();
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/R8OptionsMetadata.java b/src/main/java/com/android/tools/r8/metadata/R8OptionsMetadata.java
index f50b85b..810f37a 100644
--- a/src/main/java/com/android/tools/r8/metadata/R8OptionsMetadata.java
+++ b/src/main/java/com/android/tools/r8/metadata/R8OptionsMetadata.java
@@ -14,6 +14,12 @@
   R8ApiModelingMetadata getApiModelingMetadata();
 
   /**
+   * @return null if the underlying metadata is generated with a version of R8 that does not emit
+   *     {@link R8KeepAnnotationsMetadata}. This was added in R8 9.1.26-dev.
+   */
+  R8KeepAnnotationsMetadata getKeepAnnotationsMetadata();
+
+  /**
    * @return null if no ProGuard configuration is provided.
    */
   R8KeepAttributesMetadata getKeepAttributesMetadata();
diff --git a/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java b/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java
index ed22eb6..70dadfd 100644
--- a/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java
+++ b/src/main/java/com/android/tools/r8/metadata/impl/BuildMetadataFactory.java
@@ -45,7 +45,7 @@
         virtualFilesForFeatureSplit.getOrDefault(FeatureSplit.BASE, Collections.emptyList());
     InternalOptions options = appView.options();
     return R8BuildMetadataImpl.builder()
-        .setOptions(new R8OptionsMetadataImpl(options))
+        .setOptions(new R8OptionsMetadataImpl(appView, options))
         .setBaselineProfileRewritingOptions(R8BaselineProfileRewritingMetadataImpl.create(options))
         .applyIf(
             options.isGeneratingDex(), builder -> builder.setDexFilesMetadata(baseVirtualFiles))
diff --git a/src/main/java/com/android/tools/r8/metadata/impl/R8KeepAnnotationsMetadataImpl.java b/src/main/java/com/android/tools/r8/metadata/impl/R8KeepAnnotationsMetadataImpl.java
new file mode 100644
index 0000000..8ce7913
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/metadata/impl/R8KeepAnnotationsMetadataImpl.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2026, 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.metadata.impl;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.metadata.R8KeepAnnotationsMetadata;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+@UsedByReflection(
+    description = "Keep and preserve @SerializedName for correct (de)serialization",
+    constraints = {KeepConstraint.LOOKUP},
+    constrainAnnotations = @AnnotationPattern(constant = SerializedName.class),
+    kind = KeepItemKind.CLASS_AND_FIELDS,
+    fieldAccess = {FieldAccessFlags.PRIVATE},
+    fieldAnnotatedByClassConstant = SerializedName.class)
+public class R8KeepAnnotationsMetadataImpl implements R8KeepAnnotationsMetadata {
+
+  @Expose
+  @SerializedName("numberOfKeepAnnotations")
+  private final int numberOfKeepAnnotations;
+
+  public R8KeepAnnotationsMetadataImpl(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.numberOfKeepAnnotations =
+        appView.app().isDirect() ? appView.app().asDirect().getKeepDeclarations().size() : 0;
+  }
+
+  @Override
+  public int getNumberOfKeepAnnotations() {
+    return numberOfKeepAnnotations;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/metadata/impl/R8OptionsMetadataImpl.java b/src/main/java/com/android/tools/r8/metadata/impl/R8OptionsMetadataImpl.java
index 385adfe..da3150b 100644
--- a/src/main/java/com/android/tools/r8/metadata/impl/R8OptionsMetadataImpl.java
+++ b/src/main/java/com/android/tools/r8/metadata/impl/R8OptionsMetadataImpl.java
@@ -3,12 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.metadata.impl;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
 import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
 import com.android.tools.r8.keepanno.annotations.KeepConstraint;
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
 import com.android.tools.r8.keepanno.annotations.UsedByReflection;
 import com.android.tools.r8.metadata.R8ApiModelingMetadata;
+import com.android.tools.r8.metadata.R8KeepAnnotationsMetadata;
 import com.android.tools.r8.metadata.R8KeepAttributesMetadata;
 import com.android.tools.r8.metadata.R8LibraryDesugaringMetadata;
 import com.android.tools.r8.metadata.R8OptionsMetadata;
@@ -41,6 +44,10 @@
   private final boolean hasPackageObfuscationDictionary;
 
   @Expose
+  @SerializedName("keepAnnotations")
+  private final R8KeepAnnotationsMetadata keepAnnotationsMetadata;
+
+  @Expose
   @SerializedName("keepAttributes")
   private final R8KeepAttributesMetadata keepAttributesMetadata;
 
@@ -76,7 +83,8 @@
   @SerializedName("isShrinkingEnabled")
   private final boolean isShrinkingEnabled;
 
-  public R8OptionsMetadataImpl(InternalOptions options) {
+  public R8OptionsMetadataImpl(
+      AppView<? extends AppInfoWithClassHierarchy> appView, InternalOptions options) {
     super(
         R8ApiModelingMetadataImpl.create(options),
         R8LibraryDesugaringMetadataImpl.create(options),
@@ -89,6 +97,7 @@
         hasConfiguration && !configuration.getClassObfuscationDictionary().isEmpty();
     this.hasPackageObfuscationDictionary =
         hasConfiguration && !configuration.getPackageObfuscationDictionary().isEmpty();
+    this.keepAnnotationsMetadata = new R8KeepAnnotationsMetadataImpl(appView);
     this.keepAttributesMetadata =
         hasConfiguration
             ? new R8KeepAttributesMetadataImpl(configuration.getKeepAttributes())
@@ -105,6 +114,11 @@
   }
 
   @Override
+  public R8KeepAnnotationsMetadata getKeepAnnotationsMetadata() {
+    return keepAnnotationsMetadata;
+  }
+
+  @Override
   public R8KeepAttributesMetadata getKeepAttributesMetadata() {
     return keepAttributesMetadata;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
index f38fdaf..d8ec043 100644
--- a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
@@ -43,7 +43,7 @@
     this.appView = appView;
   }
 
-  public boolean isKotlinModuleFile(DataEntryResource file) {
+  public static boolean isKotlinModuleFile(DataEntryResource file) {
     return FileUtils.isKotlinModuleFile(file.getName());
   }
 
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 2b099a6..617f475 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -69,8 +69,7 @@
   public Repackaging(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
     this.packageObfuscationMode = appView.options().getPackageObfuscationMode();
-    this.repackagingConfiguration =
-        appView.options().testing.repackagingConfigurationFactory.apply(appView);
+    this.repackagingConfiguration = new DefaultRepackagingConfiguration(appView);
   }
 
   public void run(ExecutorService executorService, Timing timing) throws ExecutionException {
@@ -132,17 +131,22 @@
   private RepackagingLens repackageClasses(
       DirectMappedDexApplication.Builder appBuilder, ExecutorService executorService)
       throws ExecutionException {
-    if (packageObfuscationMode.isNone()) {
-      return null;
-    }
     BiMap<DexType, DexType> mappings = HashBiMap.create();
     Map<String, String> packageMappings = new HashMap<>();
     Set<String> seenPackageDescriptors = new HashSet<>();
     SortedProgramPackageCollection packages =
         SortedProgramPackageCollection.createWithAllProgramClasses(appView);
-    processPackagesInDesiredLocation(packages, mappings, packageMappings, seenPackageDescriptors);
+    RepackagingResourceCollisionResolver resourceCollisionDetector =
+        RepackagingResourceCollisionResolver.create(appView, packages, packageObfuscationMode);
+    processPackagesInDesiredLocation(
+        packages, mappings, packageMappings, resourceCollisionDetector, seenPackageDescriptors);
     processRemainingPackages(
-        packages, mappings, packageMappings, seenPackageDescriptors, executorService);
+        packages,
+        mappings,
+        packageMappings,
+        resourceCollisionDetector,
+        seenPackageDescriptors,
+        executorService);
     mappings.entrySet().removeIf(entry -> entry.getKey().isIdenticalTo(entry.getValue()));
     if (mappings.isEmpty()) {
       return null;
@@ -203,13 +207,15 @@
       SortedProgramPackageCollection packages,
       BiMap<DexType, DexType> mappings,
       Map<String, String> packageMappings,
+      RepackagingResourceCollisionResolver resourceCollisionDetector,
       Set<String> seenPackageDescriptors) {
     // For each package that is already in the desired location, record all the classes from the
     // package in the mapping for collision detection.
     Iterator<ProgramPackage> iterator = packages.iterator();
     while (iterator.hasNext()) {
       ProgramPackage pkg = iterator.next();
-      if (repackagingConfiguration.isPackageInTargetLocation(pkg)) {
+      if (repackagingConfiguration.isPackageInTargetLocation(pkg, packageObfuscationMode)) {
+        assert !resourceCollisionDetector.isBlocked(pkg);
         for (DexProgramClass alreadyRepackagedClass : pkg) {
           if (!appView.appInfo().isRepackagingAllowed(alreadyRepackagedClass, appView)) {
             mappings.put(alreadyRepackagedClass.getType(), alreadyRepackagedClass.getType());
@@ -219,6 +225,7 @@
           processClass(alreadyRepackagedClass, pkg, pkg.getPackageDescriptor(), mappings);
         }
         packageMappings.put(pkg.getPackageDescriptor(), pkg.getPackageDescriptor());
+        resourceCollisionDetector.acceptRepackagedPackage(pkg);
         seenPackageDescriptors.add(pkg.getPackageDescriptor());
         iterator.remove();
       }
@@ -229,6 +236,7 @@
       SortedProgramPackageCollection packages,
       BiMap<DexType, DexType> mappings,
       Map<String, String> packageMappings,
+      RepackagingResourceCollisionResolver resourceCollisionDetector,
       Set<String> seenPackageDescriptors,
       ExecutorService executorService)
       throws ExecutionException {
@@ -242,7 +250,7 @@
           computeClassesToRepackage(pkg, packages, packagesWithClassesToRepackage, executorService);
       packagesWithClassesToRepackage.put(pkg, classesToRepackage);
       // Reserve the package name to ensure that we are not renaming to a package we cannot move.
-      if (classesToRepackage.size() != pkg.classesInPackage().size()) {
+      if (classesToRepackage.size() != pkg.size()) {
         seenPackageDescriptors.add(pkg.getPackageDescriptor());
       }
     }
@@ -252,19 +260,31 @@
         continue;
       }
       // Already processed packages should have been removed.
-      assert !repackagingConfiguration.isPackageInTargetLocation(pkg);
-      String newPackageDescriptor =
-          repackagingConfiguration.getNewPackageDescriptor(pkg, seenPackageDescriptors);
+      assert !repackagingConfiguration.isPackageInTargetLocation(pkg, packageObfuscationMode);
+      String newPackageDescriptor;
+      if (resourceCollisionDetector.isBlocked(pkg)) {
+        assert packageObfuscationMode.isRepackageClasses();
+        newPackageDescriptor =
+            repackagingConfiguration.getNewPackageDescriptor(
+                pkg, PackageObfuscationMode.FLATTEN, seenPackageDescriptors);
+      } else {
+        newPackageDescriptor =
+            repackagingConfiguration.getNewPackageDescriptor(
+                pkg, packageObfuscationMode, seenPackageDescriptors);
+      }
       for (DexProgramClass classToRepackage : classesToRepackage) {
         processClass(classToRepackage, pkg, newPackageDescriptor, mappings);
       }
       seenPackageDescriptors.add(newPackageDescriptor);
+      if (classesToRepackage.size() == pkg.size()) {
+        resourceCollisionDetector.acceptRepackagedPackage(pkg);
+      }
       // Package remapping is used for adapting resources. If we cannot repackage all classes in
       // a package then we put in the original descriptor to ensure that resources are not
       // rewritten.
       packageMappings.put(
           pkg.getPackageDescriptor(),
-          classesToRepackage.size() == pkg.classesInPackage().size()
+          classesToRepackage.size() == pkg.size()
               ? newPackageDescriptor
               : pkg.getPackageDescriptor());
     }
@@ -330,9 +350,13 @@
 
   public interface RepackagingConfiguration {
 
-    String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors);
+    String getNewPackageDescriptor(
+        ProgramPackage pkg,
+        PackageObfuscationMode packageObfuscationMode,
+        Set<String> seenPackageDescriptors);
 
-    boolean isPackageInTargetLocation(ProgramPackage pkg);
+    boolean isPackageInTargetLocation(
+        ProgramPackage pkg, PackageObfuscationMode packageObfuscationMode);
 
     DexType getRepackagedType(
         DexProgramClass clazz,
@@ -346,7 +370,6 @@
     private final AppView<AppInfoWithLiveness> appView;
     private final DexItemFactory dexItemFactory;
     private final InternalOptions options;
-    private final PackageObfuscationMode packageObfuscationMode;
     private final ProguardConfiguration proguardConfiguration;
     private final MinificationPackageNamingStrategy packageMinificationStrategy;
 
@@ -354,13 +377,15 @@
       this.appView = appView;
       this.dexItemFactory = appView.dexItemFactory();
       this.options = appView.options();
-      this.packageObfuscationMode = options.getPackageObfuscationMode();
       this.proguardConfiguration = options.getProguardConfiguration();
       this.packageMinificationStrategy = new MinificationPackageNamingStrategy(appView);
     }
 
     @Override
-    public String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors) {
+    public String getNewPackageDescriptor(
+        ProgramPackage pkg,
+        PackageObfuscationMode packageObfuscationMode,
+        Set<String> seenPackageDescriptors) {
       String newPackageDescriptor =
           DescriptorUtils.getBinaryNameFromJavaType(proguardConfiguration.getPackagePrefix());
       if (!appView.options().isMinifying()) {
@@ -392,7 +417,8 @@
     }
 
     @Override
-    public boolean isPackageInTargetLocation(ProgramPackage pkg) {
+    public boolean isPackageInTargetLocation(
+        ProgramPackage pkg, PackageObfuscationMode packageObfuscationMode) {
       String newPackageDescriptor =
           DescriptorUtils.getBinaryNameFromJavaType(proguardConfiguration.getPackagePrefix());
       if (packageObfuscationMode.isRepackageClasses()) {
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index b934d4e..0901048 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -265,7 +265,8 @@
       } else if (classesToRepackageInResolvedPackage.contains(resolvedProgramHolder)) {
         // Disallow repackaging of the current class.
         pinnedNodes.add(getNode(method.getDefinition()));
-      } else if (repackagingConfiguration.isPackageInTargetLocation(resolvedPackage)) {
+      } else if (repackagingConfiguration.isPackageInTargetLocation(
+          resolvedPackage, packageObfuscationMode)) {
         assert false : "Expected resolvedPackage to be null, but was: " + resolvedPackage;
         // Disallow repackaging of the current class.
         pinnedNodes.add(getNode(method.getDefinition()));
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingResourceCollisionResolver.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingResourceCollisionResolver.java
new file mode 100644
index 0000000..9a9869f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingResourceCollisionResolver.java
@@ -0,0 +1,203 @@
+// Copyright (c) 2026, 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.repackaging;
+
+import static com.android.tools.r8.dex.DataResourceWriter.shouldDropDataDirectoryResource;
+import static com.android.tools.r8.dex.DataResourceWriter.shouldDropDataEntryResource;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.dex.ResourceAdapter;
+import com.android.tools.r8.features.FeatureSplitConfiguration;
+import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramPackage;
+import com.android.tools.r8.graph.SortedProgramPackageCollection;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
+import com.android.tools.r8.utils.MapUtils;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class RepackagingResourceCollisionResolver {
+
+  // Map from each package to the set of packages it collides with.
+  private final Map<ProgramPackage, Set<ProgramPackage>> collisions = new HashMap<>();
+
+  private final Set<ProgramPackage> blocked = Sets.newIdentityHashSet();
+
+  private RepackagingResourceCollisionResolver() {}
+
+  private RepackagingResourceCollisionResolver(Collection<RepackagingCollision> collisions) {
+    for (RepackagingCollision collision : collisions) {
+      if (collision.size() > 1) {
+        for (ProgramPackage pkg : collision.packages) {
+          this.collisions
+              .computeIfAbsent(pkg, ignoreKey(Sets::newIdentityHashSet))
+              .addAll(collision.packages);
+        }
+      }
+    }
+  }
+
+  public static RepackagingResourceCollisionResolver create(
+      AppView<AppInfoWithLiveness> appView,
+      SortedProgramPackageCollection packages,
+      PackageObfuscationMode packageObfuscationMode) {
+    // If there is no resource consumer then collisions don't matter.
+    if (appView.options().dataResourceConsumer == null) {
+      return empty();
+    }
+
+    // If we are not repackaging then there are no collisions.
+    if (!packageObfuscationMode.isRepackageClasses()) {
+      return empty();
+    }
+
+    // Visit all data resources meanwhile repackaging all packages to the same target package and
+    // collecting the collisions.
+    InterceptingResourceAdapter adapter = new InterceptingResourceAdapter(appView, packages);
+    Map<String, RepackagingCollision> collisions = new HashMap<>();
+    InternalOptions options = appView.options();
+    forEachDataResourceProvider(
+        appView,
+        provider -> {
+          try {
+            provider.accept(
+                new Visitor() {
+                  @Override
+                  public void visit(DataDirectoryResource directory) {
+                    if (shouldDropDataDirectoryResource(directory, options)) {
+                      return;
+                    }
+                    collisions
+                        .computeIfAbsent(
+                            adapter.adaptDirectoryName(directory),
+                            ignoreKey(RepackagingCollision::new))
+                        .addInterceptedPackagesFrom(adapter);
+                  }
+
+                  @Override
+                  public void visit(DataEntryResource file) {
+                    if (shouldDropDataEntryResource(file, options)) {
+                      return;
+                    }
+                    collisions
+                        .computeIfAbsent(
+                            adapter.adaptFileNameIfNeeded(file),
+                            ignoreKey(RepackagingCollision::new))
+                        .addInterceptedPackagesFrom(adapter);
+                  }
+                });
+          } catch (ResourceException e) {
+            appView.reporter().error(new ExceptionDiagnostic(e));
+          }
+        });
+    return new RepackagingResourceCollisionResolver(collisions.values());
+  }
+
+  private static void forEachDataResourceProvider(
+      AppView<AppInfoWithLiveness> appView, Consumer<DataResourceProvider> consumer) {
+    appView.app().dataResourceProviders.forEach(consumer);
+    if (appView.options().hasFeatureSplitConfiguration()) {
+      FeatureSplitConfiguration featureSplitConfiguration =
+          appView.options().getFeatureSplitConfiguration();
+      for (DataResourceProvidersAndConsumer entry :
+          featureSplitConfiguration.getDataResourceProvidersAndConsumers()) {
+        entry.providers.forEach(consumer);
+      }
+    }
+  }
+
+  private static RepackagingResourceCollisionResolver empty() {
+    return new RepackagingResourceCollisionResolver();
+  }
+
+  // Called when a package is repackaged to the target package (e.g., the default package "").
+  // When this happens we prohibit repackaging of all packages that collide with the current one,
+  // by adding them to the `blocked` set. These packages will be subject to -flattenpackagehierarchy
+  // instead of -repackageclasses.
+  void acceptRepackagedPackage(ProgramPackage pkg) {
+    Set<ProgramPackage> pkgCollisions =
+        MapUtils.removeOrDefault(collisions, pkg, Collections.emptySet());
+    for (ProgramPackage pkgBlocked : pkgCollisions) {
+      if (pkgBlocked != pkg) {
+        blocked.add(pkgBlocked);
+        collisions.remove(pkgBlocked);
+      }
+    }
+  }
+
+  boolean isBlocked(ProgramPackage pkg) {
+    return blocked.contains(pkg);
+  }
+
+  // A resource adapter implementation that stores the set of packages that are being queried during
+  // calls to adaptDirectoryName() or adaptFileNameIfNeeded().
+  //
+  // If two resources are mapped to the same file name, then we conservatively treat the packages
+  // that were queried during the renaming as colliding.
+  //
+  // This only intercepts calls to adaptPackage() (and not also adaptType()), since repackaging does
+  // not cause any collisions among types.
+  private static class InterceptingResourceAdapter extends ResourceAdapter {
+
+    private final SortedProgramPackageCollection packages;
+
+    private final Set<ProgramPackage> interceptedPackages = Sets.newIdentityHashSet();
+
+    InterceptingResourceAdapter(
+        AppView<AppInfoWithLiveness> appView, SortedProgramPackageCollection packages) {
+      super(appView);
+      this.packages = packages;
+    }
+
+    @Override
+    public String adaptDirectoryName(DataDirectoryResource directory) {
+      assert interceptedPackages.isEmpty();
+      return super.adaptDirectoryName(directory);
+    }
+
+    @Override
+    public String adaptFileNameIfNeeded(DataEntryResource file) {
+      assert interceptedPackages.isEmpty();
+      return super.adaptFileNameIfNeeded(file);
+    }
+
+    @Override
+    protected String adaptPackage(String packageDescriptor) {
+      ProgramPackage pkg = packages.getPackage(packageDescriptor);
+      if (pkg != null) {
+        interceptedPackages.add(pkg);
+        return "";
+      }
+      return packageDescriptor;
+    }
+  }
+
+  private static class RepackagingCollision {
+
+    private final Set<ProgramPackage> packages = Sets.newIdentityHashSet();
+
+    void addInterceptedPackagesFrom(InterceptingResourceAdapter adapter) {
+      packages.addAll(adapter.interceptedPackages);
+      adapter.interceptedPackages.clear();
+    }
+
+    int size() {
+      return packages.size();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/DontObfuscateRule.java b/src/main/java/com/android/tools/r8/shaking/DontObfuscateRule.java
new file mode 100644
index 0000000..43af396
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DontObfuscateRule.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+public class DontObfuscateRule extends GlobalConfigurationRule {
+
+  public DontObfuscateRule(Origin origin, Position position) {
+    super(origin, position);
+  }
+
+  @Override
+  public String getSource() {
+    return "-dontobfuscate";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/DontOptimizeRule.java b/src/main/java/com/android/tools/r8/shaking/DontOptimizeRule.java
new file mode 100644
index 0000000..ccff3b0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DontOptimizeRule.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+public class DontOptimizeRule extends GlobalConfigurationRule {
+
+  public DontOptimizeRule(Origin origin, Position position) {
+    super(origin, position);
+  }
+
+  @Override
+  public String getSource() {
+    return "-dontoptimize";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/DontRepackageRule.java b/src/main/java/com/android/tools/r8/shaking/DontRepackageRule.java
new file mode 100644
index 0000000..bcf34bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DontRepackageRule.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+public class DontRepackageRule extends GlobalConfigurationRule {
+
+  public DontRepackageRule(Origin origin, Position position) {
+    super(origin, position);
+  }
+
+  @Override
+  public String getSource() {
+    return "-dontrepackage";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/DontShrinkRule.java b/src/main/java/com/android/tools/r8/shaking/DontShrinkRule.java
new file mode 100644
index 0000000..b87647c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DontShrinkRule.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+public class DontShrinkRule extends GlobalConfigurationRule {
+
+  public DontShrinkRule(Origin origin, Position position) {
+    super(origin, position);
+  }
+
+  @Override
+  public String getSource() {
+    return "-dontshrink";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/GlobalConfigurationRule.java
new file mode 100644
index 0000000..9cb7051
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalConfigurationRule.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+public abstract class GlobalConfigurationRule {
+
+  private Origin origin;
+  private Position position;
+
+  public GlobalConfigurationRule(Origin origin, Position position) {
+    this.origin = origin;
+    this.position = position;
+  }
+
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  public Position getPosition() {
+    return position;
+  }
+
+  public abstract String getSource();
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 03ac5f9..4397112 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -60,9 +60,10 @@
     private final Reporter reporter;
     private boolean allowAccessModification;
     private boolean ignoreWarnings;
-    private boolean optimizing = true;
-    private boolean obfuscating = true;
-    private boolean shrinking = true;
+    private final List<DontObfuscateRule> dontObfuscateRules = new ArrayList<>();
+    private final List<DontOptimizeRule> dontOptimizeRules = new ArrayList<>();
+    private final List<DontRepackageRule> dontRepackageRules = new ArrayList<>();
+    private final List<DontShrinkRule> dontShrinkRules = new ArrayList<>();
     private boolean printBlastRadius;
     private Path printBlastRadiusFile;
     private boolean printConfiguration;
@@ -186,49 +187,53 @@
 
     @Override
     public void disableOptimization(ProguardConfigurationSourceParser parser, Position position) {
-      this.optimizing = false;
+      dontOptimizeRules.add(new DontOptimizeRule(parser.getOrigin(), position));
     }
 
     @Override
     public void disableObfuscation(ProguardConfigurationSourceParser parser, Position position) {
-      this.obfuscating = false;
+      dontObfuscateRules.add(new DontObfuscateRule(parser.getOrigin(), position));
     }
 
     @Override
     public void disableRepackaging(ProguardConfigurationSourceParser parser, Position position) {
+      dontRepackageRules.add(new DontRepackageRule(parser.getOrigin(), position));
       packageObfuscationMode = PackageObfuscationMode.NONE;
     }
 
     @Override
     public void disableShrinking(ProguardConfigurationSourceParser parser, Position position) {
-      this.shrinking = false;
-    }
-
-    public Builder disableOptimization() {
-      this.optimizing = false;
-      return this;
+      dontShrinkRules.add(new DontShrinkRule(parser.getOrigin(), position));
     }
 
     public Builder disableObfuscation() {
-      this.obfuscating = false;
+      // TODO(b/479221963): Improve origin for this case.
+      dontObfuscateRules.add(new DontObfuscateRule(Origin.unknown(), Position.UNKNOWN));
+      return this;
+    }
+
+    public Builder disableOptimization() {
+      // TODO(b/479221963): Improve origin for this case.
+      dontOptimizeRules.add(new DontOptimizeRule(Origin.unknown(), Position.UNKNOWN));
+      return this;
+    }
+
+    public Builder disableShrinking() {
+      // TODO(b/479221963): Improve origin for this case.
+      dontShrinkRules.add(new DontShrinkRule(Origin.unknown(), Position.UNKNOWN));
       return this;
     }
 
     boolean isObfuscating() {
-      return obfuscating;
+      return dontObfuscateRules.isEmpty();
     }
 
     public boolean isOptimizing() {
-      return optimizing;
+      return dontOptimizeRules.isEmpty();
     }
 
     public boolean isShrinking() {
-      return shrinking;
-    }
-
-    public Builder disableShrinking() {
-      shrinking = false;
-      return this;
+      return dontShrinkRules.isEmpty();
     }
 
     @Override
@@ -521,9 +526,10 @@
               packagePrefix,
               allowAccessModification,
               ignoreWarnings,
-              optimizing,
-              obfuscating,
-              shrinking,
+              dontObfuscateRules,
+              dontOptimizeRules,
+              dontRepackageRules,
+              dontShrinkRules,
               printBlastRadius,
               printBlastRadiusFile,
               printConfiguration,
@@ -571,9 +577,10 @@
   private final String packagePrefix;
   private final boolean allowAccessModification;
   private final boolean ignoreWarnings;
-  private final boolean optimizing;
-  private final boolean obfuscating;
-  private final boolean shrinking;
+  private final List<DontObfuscateRule> dontObfuscateRules;
+  private final List<DontOptimizeRule> dontOptimizeRules;
+  private final List<DontRepackageRule> dontRepackageRules;
+  private final List<DontShrinkRule> dontShrinkRules;
   private final boolean printBlastRadius;
   private final Path printBlastRadiusFile;
   private final boolean printConfiguration;
@@ -614,9 +621,10 @@
       String packagePrefix,
       boolean allowAccessModification,
       boolean ignoreWarnings,
-      boolean optimizing,
-      boolean obfuscating,
-      boolean shrinking,
+      List<DontObfuscateRule> dontObfuscateRules,
+      List<DontOptimizeRule> dontOptimizeRules,
+      List<DontRepackageRule> dontRepackageRules,
+      List<DontShrinkRule> dontShrinkRules,
       boolean printBlastRadius,
       Path printBlastRadiusFile,
       boolean printConfiguration,
@@ -653,9 +661,10 @@
     this.packagePrefix = packagePrefix;
     this.allowAccessModification = allowAccessModification;
     this.ignoreWarnings = ignoreWarnings;
-    this.optimizing = optimizing;
-    this.obfuscating = obfuscating;
-    this.shrinking = shrinking;
+    this.dontObfuscateRules = dontObfuscateRules;
+    this.dontOptimizeRules = dontOptimizeRules;
+    this.dontRepackageRules = dontRepackageRules;
+    this.dontShrinkRules = dontShrinkRules;
     this.printBlastRadius = printBlastRadius;
     this.printBlastRadiusFile = printBlastRadiusFile;
     this.printConfiguration = printConfiguration;
@@ -747,15 +756,15 @@
   }
 
   public boolean isOptimizing() {
-    return optimizing;
+    return dontOptimizeRules.isEmpty();
   }
 
   public boolean isObfuscating() {
-    return obfuscating;
+    return dontObfuscateRules.isEmpty();
   }
 
   public boolean isShrinking() {
-    return shrinking;
+    return dontShrinkRules.isEmpty();
   }
 
   public boolean isPrintBlastRadius() {
@@ -830,6 +839,11 @@
     return rules;
   }
 
+  public Iterable<GlobalConfigurationRule> getGlobalRules() {
+    return Iterables.concat(
+        dontObfuscateRules, dontOptimizeRules, dontRepackageRules, dontShrinkRules);
+  }
+
   public List<String> getObfuscationDictionary() {
     return obfuscationDictionary;
   }
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 64d635c..b7f8efc 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -97,8 +97,6 @@
 import com.android.tools.r8.profile.startup.instrumentation.InstrumentationOptions;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration;
-import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration;
 import com.android.tools.r8.repackaging.RepackagingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.Enqueuer;
@@ -917,10 +915,6 @@
       if (isGeneratingClassFiles() && !getTestingOptions().enableRepackagingByDefaultForCf) {
         return getPackageObfuscationModeForNone();
       }
-      // TODO(b/480068080): Also enable by default when -adaptresourcefilenames is enabled.
-      if (proguardConfiguration.getAdaptResourceFilenames().isEnabled()) {
-        return getPackageObfuscationModeForNone();
-      }
       return PackageObfuscationMode.REPACKAGE;
     }
     assert packageObfuscationMode.isFlattenPackageHierarchy()
@@ -2375,9 +2369,6 @@
 
     public Consumer<String> processingContextsConsumer = null;
 
-    public Function<AppView<AppInfoWithLiveness>, RepackagingConfiguration>
-        repackagingConfigurationFactory = DefaultRepackagingConfiguration::new;
-
     public BiConsumer<AppView<?>, HorizontallyMergedClasses> horizontallyMergedClassesConsumer =
         ConsumerUtils.emptyBiConsumer();
     public Function<List<Policy>, List<Policy>> horizontalClassMergingPolicyRewriter =
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java
index ba1ab79..73d1be8 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java
@@ -120,8 +120,7 @@
         // Keep the sealed class to ensure the PermittedSubclasses attribute stays live.
         .addKeepPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
         .addKeepMainRule(TestClass.class)
-        // TODO(b/480068080): Only add -dontrepackage.
-        .addKeepRules(repackage ? "-repackageclasses" : "-dontrepackage")
+        .applyIf(!repackage, b -> b.addKeepRules("-dontrepackage"))
         .compile()
         .inspectIf(!parameters.isRandomPartialCompilation(), this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
index cc662f1..0505273 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
@@ -78,8 +78,6 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(getProgramFiles())
         .addKeepMainRule(getMainClassName())
-        // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-        .addKeepRules("-repackageclasses")
         .addHorizontallyMergedClassesInspector(inspector -> inspect(inspector, lambdasInInput))
         .allowAccessModification(allowAccessModification)
         .noClassInlining()
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/InvokeSuperToEmptyDefaultInterfaceMethodInLibraryTest.java b/src/test/java/com/android/tools/r8/memberrebinding/InvokeSuperToEmptyDefaultInterfaceMethodInLibraryTest.java
index 6f93c96..05c43ec 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/InvokeSuperToEmptyDefaultInterfaceMethodInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/InvokeSuperToEmptyDefaultInterfaceMethodInLibraryTest.java
@@ -44,8 +44,6 @@
         .addLibraryClasses(Build.class, I.class)
         .addDefaultRuntimeLibrary(parameters)
         .addKeepMainRule(Main.class)
-        // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-        .addKeepRules("-repackageclasses")
         .allowDiagnosticWarningMessages(shouldReportDiagnostic())
         .enableInliningAnnotations()
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java b/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java
index 459bf82..e38bb1d 100644
--- a/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/metadata/R8BuildMetadataTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestBuilder.KeepAnnotationLibrary;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -24,6 +25,9 @@
 import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
 import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
 import com.android.tools.r8.profile.art.model.ExternalArtProfile;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
@@ -65,7 +69,6 @@
   public void testR8() throws Exception {
     R8BuildMetadata buildMetadata =
         testForR8(parameters)
-            .addKeepMainRule(Main.class)
             .apply(this::configure)
             .addOptionsModification(this::configureVersion)
             .applyIf(
@@ -135,6 +138,7 @@
                             LibraryDesugaringSpecification.JDK11.getSpecification()))
                     .enableIsolatedSplits(true)
                     .enableOptimizedShrinking())
+        .enableExperimentalKeepAnnotations(KeepAnnotationLibrary.LEGACY)
         .collectBuildMetadata();
   }
 
@@ -186,6 +190,13 @@
     }
     // Options metadata.
     assertNotNull(buildMetadata.getOptionsMetadata());
+    assertNotNull(buildMetadata.getOptionsMetadata().getKeepAnnotationsMetadata());
+    assertEquals(
+        1,
+        buildMetadata
+            .getOptionsMetadata()
+            .getKeepAnnotationsMetadata()
+            .getNumberOfKeepAnnotations());
     assertNotNull(buildMetadata.getOptionsMetadata().getKeepAttributesMetadata());
     assertEquals(
         parameters.isCfRuntime() ? null : Integer.toString(parameters.getApiLevel().getLevel()),
@@ -247,6 +258,12 @@
     assertEquals(versionString, buildMetadata.getVersion());
   }
 
+  @KeepEdge(
+      consequences =
+          @KeepTarget(
+              kind = KeepItemKind.CLASS_AND_METHODS,
+              classConstant = Main.class,
+              methodName = "main"))
   static class Main {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java b/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java
index 6eb8bca..4faaf9b 100644
--- a/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MissingReferenceNamingClashTest.java
@@ -52,8 +52,6 @@
         .addKeepMainRule(Main.class)
         .addKeepClassAndMembersRules(Anno.class)
         .addKeepClassRulesWithAllowObfuscation(A.class)
-        // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-        .addKeepRules("-repackageclasses")
         .addKeepRuntimeVisibleAnnotations()
         .addDontWarn(descriptorToJavaType(newDescriptor))
         .compile()
diff --git a/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java b/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterCompareAndSetTest.java
similarity index 84%
rename from src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java
rename to src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterCompareAndSetTest.java
index 7c2f94dc..369b946 100644
--- a/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterCompareAndSetTest.java
@@ -11,8 +11,9 @@
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeMatchers;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -26,18 +27,23 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class AtomicFieldUpdaterInstrumentorTest extends TestBase {
+public class AtomicFieldUpdaterCompareAndSetTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return TestParameters.builder()
-        .withDexRuntimesStartingFromIncluding(
-            Version.V4_4_4) // Unsafe synthetic doesn't work for 4.0.4.
-        .withAllApiLevels()
-        .build();
+  @Parameter(1)
+  public boolean dontObfuscate;
+
+  @Parameters(name = "{0}, dontObfuscate:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        TestParameters.builder()
+            .withDexRuntimesStartingFromIncluding(
+                Version.V4_4_4) // Unsafe synthetic doesn't work for 4.0.4.
+            .withAllApiLevels()
+            .build(),
+        BooleanUtils.values());
   }
 
   @Test
@@ -54,6 +60,7 @@
         .addProgramClasses(testClass)
         .allowDiagnosticInfoMessages()
         .addKeepMainRule(testClass)
+        .applyIf(dontObfuscate, TestShrinkerBuilder::addDontObfuscate)
         .compile()
         .inspectDiagnosticMessages(
             diagnostics -> {
@@ -88,7 +95,7 @@
                       ImmutableList.of(
                           "java.lang.Object", "long", "java.lang.Object", "java.lang.Object")));
             })
-        .run(parameters.getRuntime(), TestClass.class)
+        .run(parameters.getRuntime(), testClass)
         .assertSuccessWithOutputLines("true");
   }
 
diff --git a/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java b/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterGetTest.java
similarity index 79%
copy from src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java
copy to src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterGetTest.java
index 7c2f94dc..a220e21 100644
--- a/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterInstrumentorTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/AtomicFieldUpdaterGetTest.java
@@ -11,8 +11,9 @@
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeMatchers;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -26,18 +27,23 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class AtomicFieldUpdaterInstrumentorTest extends TestBase {
+public class AtomicFieldUpdaterGetTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return TestParameters.builder()
-        .withDexRuntimesStartingFromIncluding(
-            Version.V4_4_4) // Unsafe synthetic doesn't work for 4.0.4.
-        .withAllApiLevels()
-        .build();
+  @Parameter(1)
+  public boolean dontObfuscate;
+
+  @Parameters(name = "{0}, dontObfuscate:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        TestParameters.builder()
+            .withDexRuntimesStartingFromIncluding(
+                Version.V4_4_4) // Unsafe synthetic doesn't work for 4.0.4.
+            .withAllApiLevels()
+            .build(),
+        BooleanUtils.values());
   }
 
   @Test
@@ -54,6 +60,7 @@
         .addProgramClasses(testClass)
         .allowDiagnosticInfoMessages()
         .addKeepMainRule(testClass)
+        .applyIf(dontObfuscate, TestShrinkerBuilder::addDontObfuscate)
         .compile()
         .inspectDiagnosticMessages(
             diagnostics -> {
@@ -82,14 +89,13 @@
               assertThat(
                   method,
                   CodeMatchers.invokesMethod(
-                      "boolean",
+                      "java.lang.Object",
                       "sun.misc.Unsafe",
-                      "compareAndSwapObject",
-                      ImmutableList.of(
-                          "java.lang.Object", "long", "java.lang.Object", "java.lang.Object")));
+                      "getObjectVolatile",
+                      ImmutableList.of("java.lang.Object", "long")));
             })
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("true");
+        .run(parameters.getRuntime(), testClass)
+        .assertSuccessWithOutputLines("Hello");
   }
 
   // Corresponding to simple kotlin usage of `atomic("Hello")` via atomicfu.
@@ -110,7 +116,7 @@
     }
 
     public static void main(String[] args) {
-      System.out.println(myString$FU.compareAndSet(new TestClass(), "Hello", "World!"));
+      System.out.println(myString$FU.get(new TestClass()));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/repackage/AdaptResourceFileNamesRepackageCollisionTest.java b/src/test/java/com/android/tools/r8/repackage/AdaptResourceFileNamesRepackageCollisionTest.java
index 3a1493c..d94f4b6 100644
--- a/src/test/java/com/android/tools/r8/repackage/AdaptResourceFileNamesRepackageCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/AdaptResourceFileNamesRepackageCollisionTest.java
@@ -48,15 +48,17 @@
         .compile()
         .inspect(
             inspector -> {
-              // TODO(b/480068080): Enable repackaging by default when -adaptresourcefilenames is
-              //  enabled. Account for resource collisions in repackaging.
+              // Class A should be repackaged to the default package "".
               ClassSubject aClass = inspector.clazz("pkg1.A");
               assertThat(aClass, isPresent());
-              assertEquals("a", aClass.getDexProgramClass().getType().getPackageName());
+              assertEquals("", aClass.getDexProgramClass().getType().getPackageName());
 
+              // Due to the collision between pkg1/build.properties and pkg2/build.properties,
+              // class B should not be repackaged to the default package "".
+              // We fallback to -flattenpackagehierarchy and move it to package "a".
               ClassSubject bClass = inspector.clazz("pkg2.B");
               assertThat(bClass, isPresent());
-              assertEquals("b", bClass.getDexProgramClass().getType().getPackageName());
+              assertEquals("a", bClass.getDexProgramClass().getType().getPackageName());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello, world!");
diff --git a/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java b/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java
index 4c6b744..73e663d 100644
--- a/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java
@@ -39,8 +39,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-        .addKeepRules("-repackageclasses")
         .addKeepAttributeLineNumberTable()
         .addKeepAttributeSourceFile()
         .addHorizontallyMergedClassesInspector(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/HorizontalClassMergingStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/HorizontalClassMergingStackSampleRetraceTest.java
index e20575b..0a43112 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacksamples/HorizontalClassMergingStackSampleRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/HorizontalClassMergingStackSampleRetraceTest.java
@@ -57,8 +57,6 @@
         testBuilder ->
             testBuilder
                 .addProgramClassFileData(programClassFileData)
-                // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-                .addKeepRules("-repackageclasses")
                 .addOptionsModification(
                     options -> options.inlinerOptions().enableConstructorInlining = false)
                 .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithInlinePositionsStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithInlinePositionsStackSampleRetraceTest.java
index 1dfb7d7..780fd12 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithInlinePositionsStackSampleRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithInlinePositionsStackSampleRetraceTest.java
@@ -38,8 +38,6 @@
         testBuilder ->
             testBuilder
                 .addProgramClassFileData(programClassFileData)
-                // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-                .addKeepRules("-repackageclasses")
                 .enableInliningAnnotations());
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithOverloadStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithOverloadStackSampleRetraceTest.java
index 54f9737..8e9b80d 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithOverloadStackSampleRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithOverloadStackSampleRetraceTest.java
@@ -65,8 +65,6 @@
         testBuilder ->
             testBuilder
                 .addProgramClassFileData(programClassFileData)
-                // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-                .addKeepRules("-repackageclasses")
                 .addDontOptimize()
                 .applyIf(
                     keep,
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithRemovedArgumentStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithRemovedArgumentStackSampleRetraceTest.java
index c7da7b2..9b4bef0 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithRemovedArgumentStackSampleRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/MethodWithRemovedArgumentStackSampleRetraceTest.java
@@ -40,8 +40,6 @@
         testBuilder ->
             testBuilder
                 .addProgramClassFileData(programClassFileData)
-                // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-                .addKeepRules("-repackageclasses")
                 .enableInliningAnnotations()
                 .enableNeverClassInliningAnnotations());
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/StaticizedMethodStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/StaticizedMethodStackSampleRetraceTest.java
index edf5000..7574d25 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacksamples/StaticizedMethodStackSampleRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/StaticizedMethodStackSampleRetraceTest.java
@@ -42,8 +42,6 @@
         testBuilder ->
             testBuilder
                 .addProgramClassFileData(programClassFileData)
-                // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-                .addKeepRules("-repackageclasses")
                 .enableInliningAnnotations()
                 .enableNeverClassInliningAnnotations());
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacksamples/VerticalClassMergingStackSampleRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacksamples/VerticalClassMergingStackSampleRetraceTest.java
index bb2fc16..7651c90 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacksamples/VerticalClassMergingStackSampleRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacksamples/VerticalClassMergingStackSampleRetraceTest.java
@@ -57,8 +57,6 @@
                 .addVerticallyMergedClassesInspector(
                     inspector ->
                         inspector.assertMergedIntoSubtype(A.class).assertNoOtherClassesMerged())
-                // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-                .addKeepRules("-repackageclasses")
                 .enableInliningAnnotations()
                 .enableNeverClassInliningAnnotations()
                 .enableNoMethodStaticizingAnnotations());
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index ec4d5f4..c76b8c1 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -124,8 +124,6 @@
     testForR8(backend)
         .addProgramFiles(getProgramFiles(test))
         .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
-        // TODO(b/480068080): Should not need to explicitly add -repackageclasses.
-        .addKeepRules("-repackageclasses")
         .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
         .setMinApi(minApi)
         .compile()
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index 0e14cde..1353022 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -1708,15 +1708,8 @@
   public static ProguardConfiguration loadProguardConfiguration(
       DexItemFactory factory, List<Path> configPaths) {
     Reporter reporter = new Reporter();
+    assert !configPaths.isEmpty();
     ProguardConfiguration.Builder builder = ProguardConfiguration.builder(factory, reporter);
-    if (configPaths.isEmpty()) {
-      return builder
-          .disableShrinking()
-          .disableObfuscation()
-          .disableOptimization()
-          .addKeepAttributePatterns(ImmutableList.of("*"))
-          .build();
-    }
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(factory, reporter, builder);
     for (Path configPath : configPaths) {