Merge "Pass lense from LambdaRewriter to Outliner."
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
index 4eead29..99c97a4 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -87,7 +87,7 @@
     this.include = include;
   }
 
-  private void readArchive(ArchiveEntryConsumer consumer) throws IOException {
+  void readArchive(ArchiveEntryConsumer consumer) throws IOException {
     try (ZipFile zipFile = supplier.open()) {
       final Enumeration<? extends ZipEntry> entries = zipFile.entries();
       while (entries.hasMoreElements()) {
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 5980a0c..1a59ef8 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -136,6 +136,12 @@
       mode = defaultCompilationMode();
     }
 
+    // Internal constructor for testing.
+    Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+      super(AndroidApp.builder(app, new Reporter(diagnosticsHandler)));
+      mode = defaultCompilationMode();
+    }
+
     /**
      * Get current compilation mode.
      */
diff --git a/src/main/java/com/android/tools/r8/DataEntryResource.java b/src/main/java/com/android/tools/r8/DataEntryResource.java
index b6443a4..6b8f9c5 100644
--- a/src/main/java/com/android/tools/r8/DataEntryResource.java
+++ b/src/main/java/com/android/tools/r8/DataEntryResource.java
@@ -35,6 +35,10 @@
     return new ZipDataEntryResource(zip, entry);
   }
 
+  default DataEntryResource withName(String name) {
+    return new NestedDataEntryResource(name, null, this);
+  }
+
   class ByteDataEntryResource implements DataEntryResource {
 
     private final byte[] bytes;
@@ -124,4 +128,35 @@
       }
     }
   }
+
+  /**
+   * A resource that has the same contents as another resource, but with a different name or origin.
+   */
+  class NestedDataEntryResource implements DataEntryResource {
+
+    private final String name;
+    private final Origin origin;
+    private final DataEntryResource resource;
+
+    public NestedDataEntryResource(String name, Origin origin, DataEntryResource resource) {
+      this.name = name;
+      this.origin = origin;
+      this.resource = resource;
+    }
+
+    @Override
+    public InputStream getByteStream() throws ResourceException {
+      return resource.getByteStream();
+    }
+
+    @Override
+    public String getName() {
+      return name != null ? name : resource.getName();
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return origin != null ? origin : resource.getOrigin();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4eb4b37..e3904fb 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -83,6 +83,10 @@
       super(app);
     }
 
+    private Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+      super(app, diagnosticsHandler);
+    }
+
     // Internal
 
     void internalForceProguardCompatibility() {
@@ -455,6 +459,11 @@
     return new Builder(app);
   }
 
+  // Internal builder to start from an existing AndroidApp.
+  static Builder builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+    return new Builder(app, diagnosticsHandler);
+  }
+
   /**
    * Parse the R8 command-line.
    *
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 3d18ea3..f9e95ec 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -34,15 +34,18 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ObjectArrays;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -309,7 +312,6 @@
     }
     DataResourceConsumer dataResourceConsumer = options.dataResourceConsumer;
     if (dataResourceConsumer != null) {
-
       List<DataResourceProvider> dataResourceProviders = application.programResourceProviders
           .stream()
           .map(ProgramResourceProvider::getDataResourceProvider)
@@ -318,6 +320,7 @@
 
       ResourceAdapter resourceAdapter =
           new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+      Set<String> generatedResourceNames = new HashSet<>();
 
       for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
         try {
@@ -331,8 +334,13 @@
 
                 @Override
                 public void visit(DataEntryResource file) {
-                  dataResourceConsumer.accept(
-                      resourceAdapter.adaptFileContentsIfNeeded(file), options.reporter);
+                  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();
                 }
               });
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 e325933..673fa6e 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -18,6 +19,8 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.io.ByteStreams;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntStack;
 import java.io.InputStream;
 import java.nio.charset.Charset;
 
@@ -39,28 +42,59 @@
     this.options = options;
   }
 
-  public DataEntryResource adaptFileContentsIfNeeded(DataEntryResource file) {
-    ProguardPathFilter filter = options.proguardConfiguration.getAdaptResourceFileContents();
-    return filter.isEnabled()
-            && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
-            && filter.matches(file.getName())
-        ? adaptFileContents(file)
-        : file;
+  public DataEntryResource adaptIfNeeded(DataEntryResource file) {
+    // Adapt name, if needed.
+    ProguardPathFilter adaptResourceFileNamesFilter =
+        options.proguardConfiguration.getAdaptResourceFilenames();
+    String name =
+        adaptResourceFileNamesFilter.isEnabled()
+                && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+                && adaptResourceFileNamesFilter.matches(file.getName())
+            ? adaptFilename(file)
+            : file.getName();
+    assert name != null;
+    // Adapt contents, if needed.
+    ProguardPathFilter adaptResourceFileContentsFilter =
+        options.proguardConfiguration.getAdaptResourceFileContents();
+    byte[] contents =
+        adaptResourceFileContentsFilter.isEnabled()
+                && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+                && adaptResourceFileContentsFilter.matches(file.getName())
+            ? adaptFileContents(file)
+            : null;
+    // Return a new resource if the name or contents changed. Otherwise return the original
+    // resource as it was.
+    if (contents != null) {
+      // File contents was adapted. Return a new resource that has the new contents, and a new name,
+      // if the filename was adapted.
+      return DataEntryResource.fromBytes(contents, name, file.getOrigin());
+    }
+    if (!name.equals(file.getName())) {
+      // File contents was not adapted, but filename was.
+      return file.withName(name);
+    }
+    // Neither file contents nor filename was adapted.
+    return file;
+  }
+
+  private String adaptFilename(DataEntryResource file) {
+    FilenameAdapter adapter = new FilenameAdapter(file.getName());
+    if (adapter.run()) {
+      return adapter.getResult();
+    }
+    return file.getName();
   }
 
   // According to the Proguard documentation, the resource files should be parsed and written using
   // the platform's default character set.
-  private DataEntryResource adaptFileContents(DataEntryResource file) {
+  private byte[] adaptFileContents(DataEntryResource file) {
     try (InputStream in = file.getByteStream()) {
       byte[] bytes = ByteStreams.toByteArray(in);
       String contents = new String(bytes, Charset.defaultCharset());
 
       FileContentsAdapter adapter = new FileContentsAdapter(contents);
       if (adapter.run()) {
-        return DataEntryResource.fromBytes(
-            adapter.getResult().getBytes(Charset.defaultCharset()),
-            file.getName(),
-            file.getOrigin());
+        return adapter.getResult().getBytes(Charset.defaultCharset());
       }
     } catch (ResourceException e) {
       options.reporter.error(
@@ -68,12 +102,14 @@
     } catch (Exception e) {
       options.reporter.error(new ExceptionDiagnostic(e, file.getOrigin()));
     }
-    return file;
+    // Return null to signal that the file contents did not change. Otherwise we would have to copy
+    // the original file for no reason.
+    return null;
   }
 
-  private class FileContentsAdapter {
+  private abstract class StringAdapter {
 
-    private final String contents;
+    protected final String contents;
     private final StringBuilder result = new StringBuilder();
 
     // If any type names in `contents` have been updated. If this flag is still true in the end,
@@ -82,8 +118,21 @@
     private int outputFrom = 0;
     private int position = 0;
 
-    public FileContentsAdapter(String contents) {
+    // When renaming Java type names, the adapter always looks for the longest name to rewrite.
+    // For example, if there is a resource with the name "foo/bar/C$X$Y.txt", then the adapter will
+    // check if there is a renaming for the type "foo.bar.C$X$Y". If there is no such renaming, then
+    // -adaptresourcefilenames works in such a way that "foo/bar/C$X" should be rewritten if there
+    // is a renaming for the type "foo.bar.C$X". Therefore, when scanning forwards to read the
+    // substring "foo/bar/C$X$Y", this adapter records the positions of the two '$' characters in
+    // the stack `prefixEndPositionsExclusive`, such that it can easily backtrack to the previously
+    // valid, but shorter Java type name.
+    //
+    // Note that there is no backtracking for -adaptresourcefilecontents.
+    private final IntStack prefixEndPositionsExclusive;
+
+    public StringAdapter(String contents) {
       this.contents = contents;
+      this.prefixEndPositionsExclusive = allowRenamingOfPrefixes() ? new IntArrayList() : null;
     }
 
     public boolean run() {
@@ -123,17 +172,29 @@
         return;
       }
 
+      assert !allowRenamingOfPrefixes() || prefixEndPositionsExclusive.isEmpty();
+
       assert Character.isJavaIdentifierPart(contents.charAt(position));
       int start = position++;
       while (!eof()) {
         char currentChar = contents.charAt(position);
         if (Character.isJavaIdentifierPart(currentChar)) {
+          if (allowRenamingOfPrefixes()
+              && shouldRecordPrefix(currentChar)
+              && isRenamingCandidate(start, position)) {
+            prefixEndPositionsExclusive.push(position);
+          }
           position++;
           continue;
         }
-        if (currentChar == '.'
+        if (currentChar == getClassNameSeparator()
             && !eof(position + 1)
             && Character.isJavaIdentifierPart(contents.charAt(position + 1))) {
+          if (allowRenamingOfPrefixes()
+              && shouldRecordPrefix(currentChar)
+              && isRenamingCandidate(start, position)) {
+            prefixEndPositionsExclusive.push(position);
+          }
           // Consume the dot and the Java identifier part that follows the dot.
           position += 2;
           continue;
@@ -143,13 +204,29 @@
         break;
       }
 
-      if ((start > 0 && isDashOrDot(contents.charAt(start - 1)))
-          || (!eof(position) && isDashOrDot(contents.charAt(position)))) {
-        // The Java type starts with '-' or '.', and should not be renamed.
-        return;
+      boolean renamingSucceeded =
+          isRenamingCandidate(start, position) && renameJavaTypeInRange(start, position);
+      if (!renamingSucceeded && allowRenamingOfPrefixes()) {
+        while (!prefixEndPositionsExclusive.isEmpty() && !renamingSucceeded) {
+          int prefixEndExclusive = prefixEndPositionsExclusive.popInt();
+          assert isRenamingCandidate(start, prefixEndExclusive);
+          renamingSucceeded = handlePrefix(start, prefixEndExclusive);
+        }
       }
 
-      String javaType = contents.substring(start, position);
+      if (allowRenamingOfPrefixes()) {
+        while (!prefixEndPositionsExclusive.isEmpty()) {
+          prefixEndPositionsExclusive.popInt();
+        }
+      }
+    }
+
+    // Returns true if the Java type in the range [from; toExclusive[ was renamed.
+    protected boolean renameJavaTypeInRange(int from, int toExclusive) {
+      String javaType = contents.substring(from, toExclusive);
+      if (getClassNameSeparator() != '.') {
+        javaType = javaType.replace(getClassNameSeparator(), '.');
+      }
       DexString descriptor =
           dexItemFactory.lookupString(
               DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(javaType));
@@ -157,36 +234,143 @@
       if (dexType != null) {
         DexString renamedDescriptor = namingLense.lookupDescriptor(graphLense.lookupType(dexType));
         if (!descriptor.equals(renamedDescriptor)) {
-          // Need to flush all changes up to and excluding 'start', and then output the renamed
+          String renamedJavaType =
+              DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString());
+          // Need to flush all changes up to and excluding 'from', and then output the renamed
           // type.
-          outputRangeFromInput(outputFrom, start);
-          outputString(DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString()));
-          outputFrom = position;
+          outputRangeFromInput(outputFrom, from);
+          outputJavaType(
+              getClassNameSeparator() != '.'
+                  ? renamedJavaType.replace('.', getClassNameSeparator())
+                  : renamedJavaType);
+          outputFrom = toExclusive;
           changed = true;
+          return true;
         }
       }
+      return false;
     }
 
-    private boolean isDashOrDot(char c) {
-      return c == '.' || c == '-';
+    // Returns true if the Java package in the range [from; toExclusive[ was renamed.
+    protected boolean renameJavaPackageInRange(int from, int toExclusive) {
+      String javaPackage = contents.substring(from, toExclusive);
+      if (getClassNameSeparator() != '/') {
+        javaPackage = javaPackage.replace(getClassNameSeparator(), '/');
+      }
+      String minifiedJavaPackage = namingLense.lookupPackageName(javaPackage);
+      if (!javaPackage.equals(minifiedJavaPackage)) {
+        outputRangeFromInput(outputFrom, from);
+        outputJavaType(
+            getClassNameSeparator() != '/'
+                ? minifiedJavaPackage.replace('/', getClassNameSeparator())
+                : minifiedJavaPackage);
+        outputFrom = toExclusive;
+        changed = true;
+        return true;
+      }
+      return false;
     }
 
+    protected abstract char getClassNameSeparator();
+
+    protected abstract boolean allowRenamingOfPrefixes();
+
+    protected abstract boolean shouldRecordPrefix(char c);
+
+    protected abstract boolean handlePrefix(int from, int toExclusive);
+
+    protected abstract boolean isRenamingCandidate(int from, int toExclusive);
+
     private void outputRangeFromInput(int from, int toExclusive) {
       if (from < toExclusive) {
         result.append(contents.substring(from, toExclusive));
       }
     }
 
-    private void outputString(String s) {
+    private void outputJavaType(String s) {
       result.append(s);
     }
 
-    private boolean eof() {
+    protected boolean eof() {
       return eof(position);
     }
 
-    private boolean eof(int position) {
+    protected boolean eof(int position) {
       return position == contents.length();
     }
   }
+
+  private class FileContentsAdapter extends StringAdapter {
+
+    public FileContentsAdapter(String fileContents) {
+      super(fileContents);
+    }
+
+    @Override
+    public char getClassNameSeparator() {
+      return '.';
+    }
+
+    @Override
+    public boolean allowRenamingOfPrefixes() {
+      return false;
+    }
+
+    @Override
+    public boolean shouldRecordPrefix(char c) {
+      throw new Unreachable();
+    }
+
+    @Override
+    protected boolean handlePrefix(int from, int toExclusive) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public boolean isRenamingCandidate(int from, int toExclusive) {
+      // If the Java type starts with '-' or '.', it should not be renamed.
+      return (from <= 0 || !isDashOrDot(contents.charAt(from - 1)))
+          && (eof(toExclusive) || !isDashOrDot(contents.charAt(toExclusive)));
+    }
+
+    private boolean isDashOrDot(char c) {
+      return c == '.' || c == '-';
+    }
+  }
+
+  private class FilenameAdapter extends StringAdapter {
+
+    public FilenameAdapter(String filename) {
+      super(filename);
+    }
+
+    @Override
+    public char getClassNameSeparator() {
+      return '/';
+    }
+
+    @Override
+    public boolean allowRenamingOfPrefixes() {
+      return true;
+    }
+
+    @Override
+    public boolean shouldRecordPrefix(char c) {
+      return !Character.isLetterOrDigit(c);
+    }
+
+    @Override
+    protected boolean handlePrefix(int from, int toExclusive) {
+      assert !eof(toExclusive);
+      if (contents.charAt(toExclusive) == '/') {
+        return renameJavaPackageInRange(from, toExclusive);
+      }
+      return renameJavaTypeInRange(from, toExclusive);
+    }
+
+    @Override
+    public boolean isRenamingCandidate(int from, int toExclusive) {
+      return from == 0 && !eof(toExclusive);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 01fff01..bf5dd5d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.lang.reflect.GenericSignatureFormatError;
@@ -38,6 +39,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -92,7 +94,18 @@
     states.computeIfAbsent("", k -> topLevelState);
   }
 
-  Map<DexType, DexString> computeRenaming(Timing timing) {
+  static class ClassRenaming {
+    protected final Map<String, String> packageRenaming;
+    protected final Map<DexType, DexString> classRenaming;
+
+    private ClassRenaming(
+        Map<DexType, DexString> classRenaming, Map<String, String> packageRenaming) {
+      this.classRenaming = classRenaming;
+      this.packageRenaming = packageRenaming;
+    }
+  }
+
+  ClassRenaming computeRenaming(Timing timing) {
     // Use deterministic class order to make sure renaming is deterministic.
     Iterable<DexProgramClass> classes = appInfo.classesWithDeterministicOrder();
     // Collect names we have to keep.
@@ -128,7 +141,19 @@
     appInfo.dexItemFactory.forAllTypes(this::renameArrayTypeIfNeeded);
     timing.end();
 
-    return Collections.unmodifiableMap(renaming);
+    return new ClassRenaming(Collections.unmodifiableMap(renaming), getPackageRenaming());
+  }
+
+  private Map<String, String> getPackageRenaming() {
+    ImmutableMap.Builder<String, String> packageRenaming = ImmutableMap.builder();
+    for (Entry<String, Namespace> entry : states.entrySet()) {
+      String originalPackageName = entry.getKey();
+      String minifiedPackageName = entry.getValue().getPackageName();
+      if (!minifiedPackageName.equals(originalPackageName)) {
+        packageRenaming.put(originalPackageName, minifiedPackageName);
+      }
+    }
+    return packageRenaming.build();
   }
 
   private void renameDanglingTypes(DexClass clazz) {
@@ -406,6 +431,7 @@
 
   private class Namespace {
 
+    private final String packageName;
     private final char[] packagePrefix;
     private int typeCounter = 1;
     private int packageCounter = 1;
@@ -417,6 +443,7 @@
     }
 
     Namespace(String packageName, String separator) {
+      this.packageName = packageName;
       this.packagePrefix = ("L" + packageName
           // L or La/b/ (or La/b/C$)
           + (packageName.isEmpty() ? "" : separator))
@@ -425,6 +452,10 @@
       this.classDictionaryIterator = classDictionary.iterator();
     }
 
+    public String getPackageName() {
+      return packageName;
+    }
+
     private String nextSuggestedNameForClass() {
       StringBuilder nextName = new StringBuilder();
       if (classDictionaryIterator.hasNext()) {
@@ -466,7 +497,6 @@
       usedPackagePrefixes.add(candidate);
       return candidate;
     }
-
   }
 
   private class GenericSignatureRewriter implements GenericSignatureAction<DexType> {
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 382ac92..10aa8af 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -42,8 +43,8 @@
   public NamingLens run(Timing timing) {
     assert options.enableMinification;
     timing.begin("MinifyClasses");
-    Map<DexType, DexString> classRenaming =
-        new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
+    ClassNameMinifier classNameMinifier = new ClassNameMinifier(appInfo, rootSet, options);
+    ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
     timing.end();
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
@@ -53,7 +54,12 @@
     Map<DexField, DexString> fieldRenaming =
         new FieldNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
     timing.end();
-    NamingLens lens = new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+    NamingLens lens =
+        new MinifiedRenaming(
+            classRenaming,
+            methodRenaming,
+            fieldRenaming,
+            appInfo);
     timing.begin("MinifyIdentifiers");
     new IdentifierMinifier(appInfo, options.proguardConfiguration.getAdaptClassStrings(), lens)
         .run();
@@ -64,21 +70,28 @@
   private static class MinifiedRenaming extends NamingLens {
 
     private final AppInfo appInfo;
+    private final Map<String, String> packageRenaming;
     private final Map<DexItem, DexString> renaming = new IdentityHashMap<>();
 
     private MinifiedRenaming(
-        Map<DexType, DexString> classRenaming,
+        ClassRenaming classRenaming,
         MethodRenaming methodRenaming,
         Map<DexField, DexString> fieldRenaming,
         AppInfo appInfo) {
       this.appInfo = appInfo;
-      renaming.putAll(classRenaming);
+      this.packageRenaming = classRenaming.packageRenaming;
+      renaming.putAll(classRenaming.classRenaming);
       renaming.putAll(methodRenaming.renaming);
       renaming.putAll(methodRenaming.callSiteRenaming);
       renaming.putAll(fieldRenaming);
     }
 
     @Override
+    public String lookupPackageName(String packageName) {
+      return packageRenaming.getOrDefault(packageName, packageName);
+    }
+
+    @Override
     public DexString lookupDescriptor(DexType type) {
       return renaming.getOrDefault(type, type.descriptor);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index f1f13b3..47459c0 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -30,6 +30,8 @@
  */
 public abstract class NamingLens {
 
+  public abstract String lookupPackageName(String packageName);
+
   public abstract DexString lookupDescriptor(DexType type);
 
   public abstract String lookupSimpleName(DexType inner, DexString innerName);
@@ -99,6 +101,11 @@
     }
 
     @Override
+    public String lookupPackageName(String packageName) {
+      return packageName;
+    }
+
+    @Override
     void forAllRenamedTypes(Consumer<DexType> consumer) {
       // Intentionally left empty.
     }
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 05539fe..599d444 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -55,7 +55,8 @@
     private Origin keepParameterNamesOptionOrigin;
     private Position keepParameterNamesOptionPosition;
     private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
-    private final ProguardPathFilter.Builder adaptResourceFilenames = ProguardPathFilter.builder();
+    private final ProguardPathFilter.Builder adaptResourceFilenames =
+        ProguardPathFilter.builder().disable();
     private final ProguardPathFilter.Builder adaptResourceFileContents =
         ProguardPathFilter.builder().disable();
     private final ProguardPathFilter.Builder keepDirectories = ProguardPathFilter.builder();
@@ -231,6 +232,10 @@
       adaptClassStrings.addPattern(pattern);
     }
 
+    public void enableAdaptResourceFilenames() {
+      adaptResourceFilenames.enable();
+    }
+
     public void addAdaptResourceFilenames(ProguardPathList pattern) {
       adaptResourceFilenames.addPattern(pattern);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 855ba98..d1a0b3d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -364,10 +364,7 @@
       } else if (acceptString("adaptclassstrings")) {
         parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
       } else if (acceptString("adaptresourcefilenames")) {
-        // TODO(76377381): Report an error until it's fully supported.
-        if (failOnPartiallyImplementedOptions) {
-          failPartiallyImplementedOption("-adaptresourcefilenames", optionStart);
-        }
+        configurationBuilder.enableAdaptResourceFilenames();
         parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
       } else if (acceptString("adaptresourcefilecontents")) {
         configurationBuilder.enableAdaptResourceFileContents();
@@ -1265,6 +1262,7 @@
     private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
       ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
       StringBuilder currentAsterisks = null;
+      int asteriskCount = 0;
       StringBuilder currentBackreference = null;
       skipWhitespace();
       int start = position;
@@ -1306,19 +1304,35 @@
           }
         } else if (currentAsterisks != null) {
           if (current == '*') {
+            // only '*', '**', and '***' are allowed.
+            // E.g., '****' should be regarded as two separate wildcards (e.g., '***' and '*')
+            if (asteriskCount >= 3) {
+              wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
+              currentAsterisks = new StringBuilder();
+              asteriskCount = 0;
+            }
             currentAsterisks.append((char) current);
+            asteriskCount++;
             end += Character.charCount(current);
             continue;
           } else {
             wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
             currentAsterisks = null;
+            asteriskCount = 0;
           }
         }
         // From now on, neither in asterisk collecting state nor back reference collecting state.
         assert currentAsterisks == null && currentBackreference == null;
         if (current == '*') {
-          currentAsterisks = new StringBuilder();
-          currentAsterisks.append((char) current);
+          if (kind == IdentifierType.CLASS_NAME) {
+            // '**' and '***' are only allowed in type name.
+            currentAsterisks = new StringBuilder();
+            currentAsterisks.append((char) current);
+            asteriskCount = 1;
+          } else {
+            // For member names, regard '**' or '***' as separate single-asterisk wildcards.
+            wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
+          }
           end += Character.charCount(current);
         } else if (current == '?' || current == '%') {
           wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
diff --git a/src/test/examples/adaptresourcefilenames/A.java b/src/test/examples/adaptresourcefilenames/A.java
new file mode 100644
index 0000000..c83e8c5
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/A.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, 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 adaptresourcefilenames;
+
+public class A {
+
+  public void method() {
+    System.out.println("In A.method()");
+  }
+}
diff --git a/src/test/examples/adaptresourcefilenames/B.java b/src/test/examples/adaptresourcefilenames/B.java
new file mode 100644
index 0000000..79725cb
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/B.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, 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 adaptresourcefilenames;
+
+public class B extends A {
+
+  private Inner inner = new Inner();
+
+  public static class Inner {
+
+    public void method() {
+      System.out.println("In Inner.method()");
+    }
+  }
+
+  public void method() {
+    System.out.println("In B.method()");
+    super.method();
+    inner.method();
+  }
+}
diff --git a/src/test/examples/adaptresourcefilenames/TestClass.java b/src/test/examples/adaptresourcefilenames/TestClass.java
new file mode 100644
index 0000000..a5e96e5
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/TestClass.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, 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 adaptresourcefilenames;
+
+import adaptresourcefilenames.pkg.C;
+import adaptresourcefilenames.pkg.innerpkg.D;
+
+public class TestClass {
+
+  public static void main(String[] args) {
+    new B().method();
+    new C().method();
+    new D().method();
+  }
+}
diff --git a/src/test/examples/adaptresourcefilenames/pkg/C.java b/src/test/examples/adaptresourcefilenames/pkg/C.java
new file mode 100644
index 0000000..93d4446
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/pkg/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, 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 adaptresourcefilenames.pkg;
+
+public class C {
+
+  public void method() {
+    System.out.println("In C.method()");
+  }
+}
diff --git a/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
new file mode 100644
index 0000000..2107547
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, 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 adaptresourcefilenames.pkg.innerpkg;
+
+public class D {
+
+  public void method() {
+    System.out.println("In D.method()");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 738f29c..d6abe44 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -230,6 +230,26 @@
     }
   }
 
+  /**
+   * Creates a new, temporary JAR that contains all the entries from the given JAR as well as the
+   * specified data resources. The given JAR is left unchanged.
+   */
+  protected Path addDataResourcesToExistingJar(
+      Path existingJar, List<DataEntryResource> dataResources) throws IOException {
+    Path newJar = File.createTempFile("app", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    try (JarOutputStream out = new JarOutputStream(new FileOutputStream(newJar.toFile()))) {
+      ArchiveProgramResourceProvider.fromArchive(existingJar)
+          .readArchive(
+              (entry, stream) -> {
+                out.putNextEntry(new ZipEntry(entry.getEntryName()));
+                ByteStreams.copy(stream, out);
+                out.closeEntry();
+              });
+      addDataResourcesToJar(out, dataResources);
+    }
+    return newJar;
+  }
+
   /** Returns a list containing all the data resources in the given app. */
   public static List<DataEntryResource> getDataResources(AndroidApp app) throws ResourceException {
     List<DataEntryResource> dataResources = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index afd407e..ecaae31 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -80,6 +80,7 @@
   public static final String EXAMPLES_KOTLIN_DIR = TESTS_DIR + "examplesKotlin/";
   public static final String TESTS_BUILD_DIR = BUILD_DIR + "test/";
   public static final String EXAMPLES_BUILD_DIR = TESTS_BUILD_DIR + "examples/";
+  public static final String EXAMPLES_CF_DIR = EXAMPLES_BUILD_DIR + "classes/";
   public static final String EXAMPLES_KOTLIN_BUILD_DIR = TESTS_BUILD_DIR + "examplesKotlin/";
   public static final String EXAMPLES_KOTLIN_RESOURCE_DIR =
       TESTS_BUILD_DIR + "kotlinR8TestResources/";
@@ -753,8 +754,11 @@
   }
 
   public static List<Path> getClassFilesForTestPackage(Package pkg) throws IOException {
-    Path dir = ToolHelper.getPackageDirectoryForTestPackage(pkg);
-    return Files.walk(dir)
+    return getClassFilesForTestDirectory(ToolHelper.getPackageDirectoryForTestPackage(pkg));
+  }
+
+  public static List<Path> getClassFilesForTestDirectory(Path directory) throws IOException {
+    return Files.walk(directory)
         .filter(path -> path.toString().endsWith(".class"))
         .collect(Collectors.toList());
   }
@@ -823,6 +827,13 @@
         .setProguardMapConsumer(StringConsumer.emptyConsumer());
   }
 
+  public static R8Command.Builder prepareR8CommandBuilder(
+      AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+    return R8Command.builder(app, diagnosticsHandler)
+        .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+        .setProguardMapConsumer(StringConsumer.emptyConsumer());
+  }
+
   public static AndroidApp runR8(AndroidApp app) throws IOException {
     return runR8WithProgramConsumer(app, DexIndexedConsumer.emptyConsumer());
   }
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index 74148c7..e3f6eac 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -42,7 +43,7 @@
 
 public class AdaptResourceFileContentsTest extends ProguardCompatabilityTestBase {
 
-  private static class CustomDataResourceConsumer implements DataResourceConsumer {
+  protected static class CustomDataResourceConsumer implements DataResourceConsumer {
 
     private final Map<String, ImmutableList<String>> resources = new HashMap<>();
 
@@ -53,6 +54,7 @@
 
     @Override
     public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+      assertFalse(resources.containsKey(file.getName()));
       try {
         byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
         String contents = new String(bytes, Charset.defaultCharset());
@@ -68,6 +70,10 @@
     public ImmutableList<String> get(String name) {
       return resources.get(name);
     }
+
+    public int size() {
+      return resources.size();
+    }
   }
 
   private static final ImmutableList<String> originalAllChangedResource =
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
new file mode 100644
index 0000000..dbe6541
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -0,0 +1,505 @@
+// Copyright (c) 2018, 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.naming;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.naming.AdaptResourceFileContentsTest.CustomDataResourceConsumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.KeepingDiagnosticHandler;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AdaptResourceFileNamesTest extends ProguardCompatabilityTestBase {
+
+  private static final Path CF_DIR =
+      Paths.get(ToolHelper.EXAMPLES_CF_DIR).resolve("adaptresourcefilenames");
+  private static final Path TEST_JAR =
+      Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
+          .resolve("adaptresourcefilenames" + FileUtils.JAR_EXTENSION);
+
+  private KeepingDiagnosticHandler diagnosticsHandler;
+  private ClassNameMapper mapper = null;
+
+  @Before
+  public void reset() {
+    diagnosticsHandler = new KeepingDiagnosticHandler();
+    mapper = null;
+  }
+
+  private static String getProguardConfig(
+      boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
+    String adaptResourceFilenamesRule;
+    if (enableAdaptResourceFileNames) {
+      adaptResourceFilenamesRule = "-adaptresourcefilenames";
+      if (adaptResourceFileNamesPathFilter != null) {
+        adaptResourceFilenamesRule += " " + adaptResourceFileNamesPathFilter;
+      }
+    } else {
+      adaptResourceFilenamesRule = "";
+    }
+    return String.join(
+        System.lineSeparator(),
+        adaptResourceFilenamesRule,
+        "-keep class " + adaptresourcefilenames.TestClass.class.getName() + " {",
+        "  public static void main(...);",
+        "}");
+  }
+
+  private static String getProguardConfigWithNeverInline(
+      boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
+    return String.join(
+        System.lineSeparator(),
+        getProguardConfig(enableAdaptResourceFileNames, adaptResourceFileNamesPathFilter),
+        "-neverinline class " + adaptresourcefilenames.A.class.getName() + " {",
+        "  public void method();",
+        "}",
+        "-neverinline class " + adaptresourcefilenames.B.Inner.class.getName() + " {",
+        "  public void method();",
+        "}");
+  }
+
+  @Test
+  public void testEnabled() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(
+        getProguardConfigWithNeverInline(true, null), dataResourceConsumer, this::checkR8Renamings);
+    // Check that the generated resources have the expected names.
+    for (DataEntryResource dataResource : getOriginalDataResources()) {
+      assertNotNull(
+          "Resource not renamed as expected: " + dataResource.getName(),
+          dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper)));
+    }
+  }
+
+  @Test
+  public void testEnabledWithFilter() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(
+        getProguardConfigWithNeverInline(true, "**.md"),
+        dataResourceConsumer,
+        this::checkR8Renamings);
+    // Check that the generated resources have the expected names.
+    Map<String, String> expectedRenamings =
+        ImmutableMap.of("adaptresourcefilenames/B.md", "adaptresourcefilenames/b.md");
+    for (DataEntryResource dataResource : getOriginalDataResources()) {
+      assertNotNull(
+          "Resource not renamed as expected: " + dataResource.getName(),
+          dataResourceConsumer.get(
+              expectedRenamings.getOrDefault(dataResource.getName(), dataResource.getName())));
+    }
+  }
+
+  @Test
+  public void testDisabled() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
+    // Check that none of the resources were renamed.
+    for (DataEntryResource dataResource : getOriginalDataResources()) {
+      assertNotNull(
+          "Resource not renamed as expected: " + dataResource.getName(),
+          dataResourceConsumer.get(dataResource.getName()));
+    }
+  }
+
+  @Test
+  public void testCollisionBehavior() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(
+        getProguardConfigWithNeverInline(true, null),
+        dataResourceConsumer,
+        this::checkR8Renamings,
+        ImmutableList.<DataEntryResource>builder()
+            .addAll(getOriginalDataResources())
+            .add(
+                DataEntryResource.fromBytes(
+                    new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
+            .build());
+    assertEquals(1, diagnosticsHandler.warnings.size());
+    assertThat(
+        diagnosticsHandler.warnings.get(0).getDiagnosticMessage(),
+        containsString("Resource 'adaptresourcefilenames/b.txt' already exists."));
+    assertEquals(getOriginalDataResources().size(), dataResourceConsumer.size());
+  }
+
+  @Test
+  public void testProguardBehavior() throws Exception {
+    Path inputJar = addDataResourcesToExistingJar(TEST_JAR, getOriginalDataResources());
+    Path proguardedJar =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    Path proguardMapFile = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
+    runProguard6Raw(proguardedJar, inputJar, getProguardConfig(true, null), proguardMapFile);
+    // Extract the names of the generated resources.
+    Set<String> filenames = new HashSet<>();
+    ArchiveResourceProvider.fromArchive(proguardedJar, true)
+        .accept(
+            new Visitor() {
+              @Override
+              public void visit(DataDirectoryResource directory) {}
+
+              @Override
+              public void visit(DataEntryResource file) {
+                filenames.add(file.getName());
+              }
+            });
+    // Check that the generated resources have the expected names.
+    ClassNameMapper mapper = ClassNameMapper.mapperFromFile(proguardMapFile);
+    for (DataEntryResource dataResource : getOriginalDataResources()) {
+      String expectedName = getExpectedRenamingFor(dataResource.getName(), mapper);
+      assertTrue(
+          "Resource not renamed to '" + expectedName + "' as expected: " + dataResource.getName(),
+          filenames.contains(expectedName));
+    }
+  }
+
+  @Test
+  public void testProguardCollisionBehavior() throws Exception {
+    List<DataEntryResource> originalDataResources = getOriginalDataResources();
+    Path inputJar =
+        addDataResourcesToExistingJar(
+            TEST_JAR,
+            ImmutableList.<DataEntryResource>builder()
+                .addAll(originalDataResources)
+                .add(
+                    DataEntryResource.fromBytes(
+                        new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
+                .build());
+    Path proguardedJar =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    runProguard6Raw(
+        proguardedJar,
+        inputJar,
+        getProguardConfig(true, null),
+        null,
+        processResult -> {
+          assertEquals(0, processResult.exitCode);
+          assertThat(
+              processResult.stderr,
+              containsString(
+                  "Warning: can't write resource [adaptresourcefilenames/b.txt] "
+                      + "(Duplicate jar entry [adaptresourcefilenames/b.txt])"));
+        });
+    assertEquals(
+        originalDataResources.size(),
+        getDataResources(ArchiveResourceProvider.fromArchive(proguardedJar, true))
+            .stream()
+            .filter(dataResource -> !dataResource.getName().equals("META-INF/MANIFEST.MF"))
+            .count());
+  }
+
+  private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
+      throws CompilationFailedException, IOException {
+    return compileWithR8(proguardConfig, dataResourceConsumer, null);
+  }
+
+  private AndroidApp compileWithR8(
+      String proguardConfig,
+      DataResourceConsumer dataResourceConsumer,
+      StringConsumer proguardMapConsumer)
+      throws CompilationFailedException, IOException {
+    return compileWithR8(
+        proguardConfig, dataResourceConsumer, proguardMapConsumer, getOriginalDataResources());
+  }
+
+  private AndroidApp compileWithR8(
+      String proguardConfig,
+      DataResourceConsumer dataResourceConsumer,
+      StringConsumer proguardMapConsumer,
+      List<DataEntryResource> dataResources)
+      throws CompilationFailedException, IOException {
+    R8Command command =
+        ToolHelper.allowTestProguardOptions(
+                ToolHelper.prepareR8CommandBuilder(getAndroidApp(dataResources), diagnosticsHandler)
+                    .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
+            .build();
+    return ToolHelper.runR8(
+        command,
+        options -> {
+          // TODO(christofferqa): Class inliner should respect -neverinline.
+          options.enableClassInlining = false;
+          options.enableClassMerging = true;
+          options.dataResourceConsumer = dataResourceConsumer;
+          options.proguardMapConsumer = proguardMapConsumer;
+        });
+  }
+
+  private void checkR8Renamings(String proguardMap, DiagnosticsHandler handler) {
+    try {
+      // Check that the renamings are as expected. These exact renamings are not important as
+      // such, but the test expectations rely on them.
+      mapper = ClassNameMapper.mapperFromString(proguardMap);
+      assertEquals(
+          "adaptresourcefilenames.TestClass",
+          mapper.deobfuscateClassName("adaptresourcefilenames.TestClass"));
+      assertEquals(
+          "adaptresourcefilenames.B", mapper.deobfuscateClassName("adaptresourcefilenames.b"));
+      assertEquals(
+          "adaptresourcefilenames.B$Inner",
+          mapper.deobfuscateClassName("adaptresourcefilenames.a"));
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private AndroidApp getAndroidApp(List<DataEntryResource> dataResources) throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder.addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR));
+    dataResources.forEach(builder::addDataResource);
+    return builder.build();
+  }
+
+  private static List<DataEntryResource> getOriginalDataResources() {
+    List<String> filenames =
+        ImmutableList.of(
+            // Filename with simple name in root directory.
+            "TestClass",
+            "B",
+            // Filename with qualified name in root directory.
+            "adaptresourcefilenames.TestClass",
+            "adaptresourcefilenames.B",
+            // Filename with qualified directory name in root directory.
+            "adaptresourcefilenames/TestClass",
+            "adaptresourcefilenames/B",
+            // Filename with simple name in sub directory.
+            "foo/bar/baz/TestClass",
+            "foo/bar/baz/B",
+            // Filename with qualified name in sub directory.
+            "foo/bar/baz/adaptresourcefiles.TestClass",
+            "foo/bar/baz/adaptresourcefiles.B",
+            // Filename with qualified directory name in sub directory.
+            "foo/bar/baz/adaptresourcefilenames/TestClass",
+            "foo/bar/baz/adaptresourcefilenames/B",
+            //
+            // SUFFIX VARIANTS:
+            //
+            // Filename with simple name and extension in root directory.
+            "TestClass.txt",
+            "B.txt",
+            // Filename with qualified name and extension in root directory.
+            "adaptresourcefilenames.TestClass.txt",
+            "adaptresourcefilenames.B.txt",
+            // Filename with qualified directory name and extension in root directory.
+            "adaptresourcefilenames/TestClass.txt",
+            "adaptresourcefilenames/B.txt",
+            // Filename with simple name and extension in sub directory.
+            "foo/bar/baz/TestClass.txt",
+            "foo/bar/baz/B.txt",
+            // Filename with qualified name and extension in sub directory.
+            "foo/bar/baz/adaptresourcefiles.TestClass.txt",
+            "foo/bar/baz/adaptresourcefiles.B.txt",
+            // Filename with qualified directory name and extension in sub directory.
+            "foo/bar/baz/adaptresourcefilenames/TestClass.txt",
+            "foo/bar/baz/adaptresourcefilenames/B.txt",
+            // Filename with other extension (used to test filtering).
+            "adaptresourcefilenames/TestClass.md",
+            "adaptresourcefilenames/B.md",
+            // Filename with dot suffix only.
+            "adaptresourcefilenames/TestClass.",
+            "adaptresourcefilenames/B.",
+            // Filename with dot suffix and extension.
+            "adaptresourcefilenames/TestClass.suffix.txt",
+            "adaptresourcefilenames/B.suffix.txt",
+            // Filename with dash suffix and extension.
+            "adaptresourcefilenames/TestClass-suffix.txt",
+            "adaptresourcefilenames/B-suffix.txt",
+            // Filename with dollar suffix and extension.
+            "adaptresourcefilenames/TestClass$suffix.txt",
+            "adaptresourcefilenames/B$suffix.txt",
+            // Filename with dollar suffix matching inner class and extension.
+            "adaptresourcefilenames/TestClass$Inner.txt",
+            "adaptresourcefilenames/B$Inner.txt",
+            // Filename with underscore suffix and extension.
+            "adaptresourcefilenames/TestClass_suffix.txt",
+            "adaptresourcefilenames/B_suffix.txt",
+            // Filename with whitespace suffix and extension.
+            "adaptresourcefilenames/TestClass suffix.txt",
+            "adaptresourcefilenames/B suffix.txt",
+            // Filename with identifier suffix and extension.
+            "adaptresourcefilenames/TestClasssuffix.txt",
+            "adaptresourcefilenames/Bsuffix.txt",
+            // Filename with numeric suffix and extension.
+            "adaptresourcefilenames/TestClass42.txt",
+            "adaptresourcefilenames/B42.txt",
+            //
+            // PREFIX VARIANTS:
+            //
+            // Filename with dot prefix and extension.
+            "adaptresourcefilenames/prefix.TestClass.txt",
+            "adaptresourcefilenames/prefix.B.txt",
+            // Filename with dash prefix and extension.
+            "adaptresourcefilenames/prefix-TestClass.txt",
+            "adaptresourcefilenames/prefix-B.txt",
+            // Filename with dollar prefix and extension.
+            "adaptresourcefilenames/prefix$TestClass.txt",
+            "adaptresourcefilenames/prefix$B.txt",
+            // Filename with identifier prefix and extension.
+            "adaptresourcefilenames/prefixTestClass.txt",
+            "adaptresourcefilenames/prefixB.txt",
+            // Filename with numeric prefix and extension.
+            "adaptresourcefilenames/42TestClass.txt",
+            "adaptresourcefilenames/42B.txt",
+            //
+            // PACKAGE RENAMING TESTS:
+            //
+            // Filename that matches a type, but only the directory should be renamed.
+            "adaptresourcefilenames/pkg/C",
+            // Filename that matches a type that should be renamed.
+            "adaptresourcefilenames/pkg/C.txt",
+            // Filename that does not match a type, but where the directory should be renamed.
+            "adaptresourcefilenames/pkg/file.txt",
+            // Filename that does not match a type, but where a directory-prefix should be renamed.
+            "adaptresourcefilenames/pkg/directory/file.txt",
+            // Filename that matches a type, but only the directory should be renamed.
+            "adaptresourcefilenames/pkg/innerpkg/D",
+            // Filename that matches a type that should be renamed.
+            "adaptresourcefilenames/pkg/innerpkg/D.txt",
+            // Filename that does not match a type, but where the directory should be renamed.
+            "adaptresourcefilenames/pkg/innerpkg/file.txt",
+            // Filename that does not match a type, but where a directory-prefix should be renamed.
+            "adaptresourcefilenames/pkg/innerpkg/directory/file.txt"
+            );
+    return filenames
+        .stream()
+        .map(filename -> DataEntryResource.fromBytes(new byte[0], filename, Origin.unknown()))
+        .collect(Collectors.toList());
+  }
+
+  private static String getExpectedRenamingFor(String filename, ClassNameMapper mapper) {
+    String typeName = null;
+    String suffix = null;
+    switch (filename) {
+        // Filename with dot only.
+      case "adaptresourcefilenames/B.":
+        typeName = "adaptresourcefilenames.B";
+        suffix = ".";
+        break;
+        // Filename with extension.
+      case "adaptresourcefilenames/B.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = ".txt";
+        break;
+        // Filename with other extension (used to test filtering).
+      case "adaptresourcefilenames/B.md":
+        typeName = "adaptresourcefilenames.B";
+        suffix = ".md";
+        break;
+        // Filename with dot suffix and extension.
+      case "adaptresourcefilenames/B.suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = ".suffix.txt";
+        break;
+        // Filename with dash suffix and extension.
+      case "adaptresourcefilenames/B-suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = "-suffix.txt";
+        break;
+        // Filename with dollar suffix and extension.
+      case "adaptresourcefilenames/B$suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = "$suffix.txt";
+        break;
+        // Filename with dollar suffix matching inner class and extension.
+      case "adaptresourcefilenames/B$Inner.txt":
+        typeName = "adaptresourcefilenames.B$Inner";
+        suffix = ".txt";
+        break;
+        // Filename with underscore suffix and extension.
+      case "adaptresourcefilenames/B_suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = "_suffix.txt";
+        break;
+        // Filename with whitespace suffix and extension.
+      case "adaptresourcefilenames/B suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = " suffix.txt";
+        break;
+        //
+        // PACKAGE RENAMING TESTS
+        //
+      case "adaptresourcefilenames/pkg/C.txt":
+        typeName = "adaptresourcefilenames.pkg.C";
+        suffix = ".txt";
+        break;
+      case "adaptresourcefilenames/pkg/innerpkg/D.txt":
+        typeName = "adaptresourcefilenames.pkg.innerpkg.D";
+        suffix = ".txt";
+        break;
+    }
+    if (typeName != null) {
+      String renamedName = mapper.getObfuscatedToOriginalMapping().inverse().get(typeName);
+      assertNotNull(renamedName);
+      assertNotEquals(typeName, renamedName);
+      return renamedName.replace('.', '/') + suffix;
+    }
+    // Renamings for files in directories that match packages that have been renamed,
+    // but where the filename itself should not be renamed.
+    String samePackageAsType = null;
+    switch (filename) {
+      case "adaptresourcefilenames/pkg/C":
+        samePackageAsType = "adaptresourcefilenames.pkg.C";
+        suffix = "C";
+        break;
+      case "adaptresourcefilenames/pkg/file.txt":
+        samePackageAsType = "adaptresourcefilenames.pkg.C";
+        suffix = "file.txt";
+        break;
+      case "adaptresourcefilenames/pkg/directory/file.txt":
+        samePackageAsType = "adaptresourcefilenames.pkg.C";
+        suffix = "directory/file.txt";
+        break;
+      case "adaptresourcefilenames/pkg/innerpkg/D":
+        samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
+        suffix = "D";
+        break;
+      case "adaptresourcefilenames/pkg/innerpkg/file.txt":
+        samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
+        suffix = "file.txt";
+        break;
+      case "adaptresourcefilenames/pkg/innerpkg/directory/file.txt":
+        samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
+        suffix = "directory/file.txt";
+        break;
+    }
+    if (samePackageAsType != null) {
+      String renamedName = mapper.getObfuscatedToOriginalMapping().inverse().get(samePackageAsType);
+      assertNotNull(renamedName);
+      assertNotEquals(samePackageAsType, renamedName);
+      if (renamedName.contains(".")) {
+        String renamedPackageName = renamedName.substring(0, renamedName.lastIndexOf('.'));
+        return renamedPackageName.replace('.', '/') + "/" + suffix;
+      }
+    }
+    return filename;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
new file mode 100644
index 0000000..05d0b6f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -0,0 +1,176 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class B111974287 {
+  B111974287 self;
+  B111974287[] clones;
+
+  B111974287() {
+    self = this;
+    clones = new B111974287[1];
+    clones[0] = self;
+  }
+
+  B111974287 fooX() {
+    System.out.println("fooX");
+    return self;
+  }
+
+  B111974287 fooYY() {
+    System.out.println("fooYY");
+    return self;
+  }
+
+  B111974287 fooZZZ() {
+    System.out.println("fooZZZ");
+    return self;
+  }
+}
+
+@RunWith(Parameterized.class)
+public class AsterisksTest extends ProguardCompatabilityTestBase {
+  private final static List<Class> CLASSES = ImmutableList.of(B111974287.class);
+  private final Shrinker shrinker;
+
+  public AsterisksTest(Shrinker shrinker) {
+    this.shrinker = shrinker;
+  }
+
+  @Parameters(name = "shrinker: {0}")
+  public static Collection<Object> data() {
+    return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8);
+  }
+
+  @Test
+  public void doubleAsterisksInField() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  ** **;",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+    fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+    // TODO(b/111974287): Proguard6 kept and renamed the field with array type.
+    if (shrinker == Shrinker.PROGUARD6) {
+      return;
+    }
+    assertThat(fieldSubject, not(isPresent()));
+  }
+
+  @Test
+  public void doubleAsterisksInMethod() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  ** foo**(...);",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    DexClass clazz = classSubject.getDexClass();
+    assertEquals(3, clazz.virtualMethods().length);
+    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      MethodSubject methodSubject =
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+      assertThat(methodSubject, isPresent());
+      assertThat(methodSubject, not(isRenamed()));
+    }
+  }
+
+  @Test
+  public void tripleAsterisksInField() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  *** ***;",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+    fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+  }
+
+  @Test
+  public void tripleAsterisksInMethod() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  *** foo***(...);",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    DexClass clazz = classSubject.getDexClass();
+    assertEquals(3, clazz.virtualMethods().length);
+    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      MethodSubject methodSubject =
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+      assertThat(methodSubject, isPresent());
+      assertThat(methodSubject, not(isRenamed()));
+    }
+  }
+
+  @Test
+  public void quadrupleAsterisksInType() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **** {",
+        "  **** foo***(...);",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    DexClass clazz = classSubject.getDexClass();
+    assertEquals(3, clazz.virtualMethods().length);
+    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      MethodSubject methodSubject =
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+      assertThat(methodSubject, isPresent());
+      assertThat(methodSubject, not(isRenamed()));
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 758e4f0..f79e01f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1810,7 +1810,6 @@
   @Test
   public void parse_partiallyImplemented_notSupported() {
     testNotSupported("-keepdirectories");
-    testNotSupported("-adaptresourcefilenames");
   }
 
   private void checkRulesSourceSnippet(List<String> sourceRules) {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
index 770bdbd..c907276 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -158,11 +158,31 @@
       Path proguardMap,
       List<DataEntryResource> dataResources)
       throws Exception {
+    return runProguard6Raw(
+        destination,
+        jarTestClasses(programClasses, dataResources),
+        proguardConfig,
+        proguardMap,
+        null);
+  }
+
+  protected ProcessResult runProguard6Raw(
+      Path destination, Path jar, String proguardConfig, Path proguardMap) throws Exception {
+    return runProguard6Raw(destination, jar, proguardConfig, proguardMap, null);
+  }
+
+  protected ProcessResult runProguard6Raw(
+      Path destination,
+      Path jar,
+      String proguardConfig,
+      Path proguardMap,
+      Consumer<ProcessResult> processResultConsumer)
+      throws Exception {
     Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
     FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
     ProcessResult result =
         ToolHelper.runProguard6Raw(
-            jarTestClasses(programClasses, dataResources),
+            jar,
             destination,
             ToolHelper.getAndroidJar(AndroidApiLevel.N),
             proguardConfigFile,
@@ -170,6 +190,9 @@
     if (result.exitCode != 0) {
       fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
     }
+    if (processResultConsumer != null) {
+      processResultConsumer.accept(result);
+    }
     return result;
   }