Version 1.5.35

Cherry pick: Add tests for class and package obfuscation dictionary
CL: https://r8-review.googlesource.com/c/r8/+/38554

Cherry pick: Unify reservation of field and method names in strategy
CL: https://r8-review.googlesource.com/c/r8/+/38555

Cherry pick: Add test for proguard map parsing error regarding
inlining frames
CL: https://r8-review.googlesource.com/c/r8/+/38160

Cherry pick: Rewrite parser to remember last added entry
CL: https://r8-review.googlesource.com/c/r8/+/38580

Cherry pick: Add test for rotating names when using applymapping
CL: https://r8-review.googlesource.com/c/r8/+/38540

Cherry pick: Change ProguardMapMinifier to handle applymapping use
cases
CL: https://r8-review.googlesource.com/c/r8/+/38556

Cherry pick: Allow for using minification with applymapping
https://r8-review.googlesource.com/c/r8/+/38583

Cherry pick: Enable -dontusemixedcaseclassnames and allow numbers in
identifiers
CL: https://r8-review.googlesource.com/c/r8/+/38703

Bug: 121305642
Bug: 126503704
Bug: 128516926
Bug: 128868424
Bug: 129493704
Bug: 130736358
Bug: 131349062
Bug: 131532229
Bug: 132666215
Bug: 132812927
Bug: 132927261
Change-Id: I5d21d0a81ed2a44282889a64ec7c3975d2db187c
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 16b75f5..ae8d4ae 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -681,8 +681,8 @@
         timing.end();
       } else {
         if (appView.appInfo().hasLiveness()) {
-          // TODO(124726014): Rewrite signature annotations in lens rewriting instead of here?
-          new GenericSignatureRewriter(appView.withLiveness()).run();
+          // TODO(b/124726014): Rewrite signature annotations in lens rewriting instead of here?
+          new GenericSignatureRewriter(appView.withLiveness()).run(appView.appInfo().classes());
         }
         namingLens = NamingLens.getIdentityLens();
       }
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 1027b71..375cc0b 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.5.34";
+  public static final String LABEL = "1.5.35";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 166ec88..a3e71e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -21,7 +21,8 @@
 import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence.Wrapper;
@@ -148,8 +149,10 @@
               appView
                   .dexItemFactory()
                   .createString(
-                      StringUtils.numberToIdentifier(
-                          count, method.method.name.toSourceString().toCharArray()));
+                      SymbolGenerationUtils.numberToIdentifier(
+                          count,
+                          MixedCasing.USE_MIXED_CASE,
+                          method.method.name.toSourceString().toCharArray()));
         } else {
           // Constructors must be named `<init>`.
           return null;
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 1b50601..8018a2d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -30,8 +30,6 @@
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -50,8 +48,6 @@
 
   private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
   private final Map<String, Namespace> states = new HashMap<>();
-  private final List<String> packageDictionary;
-  private final List<String> classDictionary;
   private final boolean keepInnerClassStructure;
 
   private final Namespace topLevelState;
@@ -69,8 +65,6 @@
     this.packageObfuscationMode = options.getProguardConfiguration().getPackageObfuscationMode();
     this.isAccessModificationAllowed =
         options.getProguardConfiguration().isAccessModificationAllowed();
-    this.packageDictionary = options.getProguardConfiguration().getPackageObfuscationDictionary();
-    this.classDictionary = options.getProguardConfiguration().getClassObfuscationDictionary();
     this.keepInnerClassStructure =
         options.getProguardConfiguration().getKeepAttributes().signature
             || options.getProguardConfiguration().getKeepAttributes().innerClasses;
@@ -134,7 +128,7 @@
     timing.end();
 
     timing.begin("rename-generic");
-    new GenericSignatureRewriter(appView, renaming).run();
+    new GenericSignatureRewriter(appView, renaming).run(classes);
     timing.end();
 
     timing.begin("rename-arrays");
@@ -356,12 +350,12 @@
     }
   }
 
-  protected class Namespace {
+  protected class Namespace implements InternalNamingState {
 
     private final String packageName;
     private final char[] packagePrefix;
-    private final Iterator<String> packageDictionaryIterator;
-    private final Iterator<String> classDictionaryIterator;
+    private int dictionaryIndex = 0;
+    private int nameIndex = 1;
 
     Namespace(String packageName) {
       this(packageName, String.valueOf(DESCRIPTOR_PACKAGE_SEPARATOR));
@@ -373,8 +367,6 @@
           // L or La/b/ (or La/b/C$)
           + (packageName.isEmpty() ? "" : separator))
           .toCharArray();
-      this.packageDictionaryIterator = packageDictionary.iterator();
-      this.classDictionaryIterator = classDictionary.iterator();
 
       // R.class in Android, which contains constant IDs to assets, can be bundled at any time.
       // Insert `R` immediately so that the class name minifier can skip that name by default.
@@ -386,63 +378,49 @@
       return packageName;
     }
 
-    private DexString nextSuggestedNameForClass(DexType type) {
-      StringBuilder nextName = new StringBuilder();
-      if (!classNamingStrategy.bypassDictionary() && classDictionaryIterator.hasNext()) {
-        nextName.append(packagePrefix).append(classDictionaryIterator.next()).append(';');
-        return appView.dexItemFactory().createString(nextName.toString());
-      } else {
-        return classNamingStrategy.next(this, type, packagePrefix);
-      }
-    }
-
     DexString nextTypeName(DexType type) {
       DexString candidate;
       do {
-        candidate = nextSuggestedNameForClass(type);
+        candidate = classNamingStrategy.next(type, packagePrefix, this);
       } while (usedTypeNames.contains(candidate));
       usedTypeNames.add(candidate);
       return candidate;
     }
 
-    private String nextSuggestedNameForSubpackage() {
-      // Note that the differences between this method and the other variant for class renaming are
-      // 1) this one uses the different dictionary and counter,
-      // 2) this one does not append ';' at the end, and
-      // 3) this one removes 'L' at the beginning to make the return value a binary form.
-      if (!packageNamingStrategy.bypassDictionary() && packageDictionaryIterator.hasNext()) {
-        StringBuilder nextName = new StringBuilder();
-        nextName.append(packagePrefix).append(packageDictionaryIterator.next());
-        return nextName.toString().substring(1);
-      } else {
-        return packageNamingStrategy.next(this, packagePrefix);
-      }
-    }
-
     String nextPackagePrefix() {
       String candidate;
       do {
-        candidate = nextSuggestedNameForSubpackage();
+        candidate = packageNamingStrategy.next(packagePrefix, this);
       } while (usedPackagePrefixes.contains(candidate));
       usedPackagePrefixes.add(candidate);
       return candidate;
     }
+
+    @Override
+    public int getDictionaryIndex() {
+      return dictionaryIndex;
+    }
+
+    @Override
+    public int incrementDictionaryIndex() {
+      return dictionaryIndex++;
+    }
+
+    @Override
+    public int incrementNameIndex(boolean isDirectMethodCall) {
+      assert !isDirectMethodCall;
+      return nameIndex++;
+    }
   }
 
   protected interface ClassNamingStrategy {
-
-    DexString next(Namespace namespace, DexType type, char[] packagePrefix);
-
-    boolean bypassDictionary();
+    DexString next(DexType type, char[] packagePrefix, InternalNamingState state);
 
     boolean noObfuscation(DexType type);
   }
 
   protected interface PackageNamingStrategy {
-
-    String next(Namespace namespace, char[] packagePrefix);
-
-    boolean bypassDictionary();
+    String next(char[] packagePrefix, InternalNamingState state);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index aeac240..437d78b 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -85,11 +85,15 @@
     for (DexClass clazz : appView.appInfo().app().asDirect().allClasses()) {
       ReservedFieldNamingState reservedNames = null;
       for (DexEncodedField field : clazz.fields()) {
-        if (shouldReserveName(clazz, field)) {
+        DexString reservedName = strategy.getReservedNameOrDefault(field, clazz, null);
+        if (reservedName != null) {
           if (reservedNames == null) {
             reservedNames = getOrCreateReservedFieldNamingState(clazz.type);
           }
-          reservedNames.markReservedDirectly(field.field);
+          reservedNames.markReservedDirectly(reservedName, field.field.type);
+          if (reservedName != field.field.name) {
+            renaming.put(field.field, reservedName);
+          }
         }
       }
 
@@ -109,17 +113,6 @@
     propagateReservedFieldNamesUpwards();
   }
 
-  private boolean shouldReserveName(DexClass clazz, DexEncodedField field) {
-    if (clazz.isLibraryClass()) {
-      return true;
-    }
-    if (!appView.options().getProguardConfiguration().hasApplyMappingFile()
-        && appView.rootSet().mayNotBeMinified(field.field, appView)) {
-      return true;
-    }
-    return false;
-  }
-
   private void propagateReservedFieldNamesUpwards() {
     BottomUpClassHierarchyTraversal.forProgramClasses(appView)
         .visit(
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
index b713582..d8cb81d 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.FieldNamingState.InternalState;
-import com.android.tools.r8.naming.MemberNamingStrategy.MemberNamingInternalState;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
@@ -51,14 +50,15 @@
     DexEncodedField encodedField = appView.appInfo().resolveField(field);
     if (encodedField != null) {
       DexClass clazz = appView.definitionFor(encodedField.field.holder);
-      if (clazz == null || clazz.isLibraryClass()) {
+      if (clazz == null) {
         return field.name;
       }
-      if (!appView.options().getProguardConfiguration().hasApplyMappingFile()
-          && appView.rootSet().mayNotBeMinified(encodedField.field, appView)) {
-        return field.name;
+      DexString reservedName = strategy.getReservedNameOrDefault(encodedField, clazz, null);
+      if (reservedName != null) {
+        return reservedName;
       }
     }
+    // TODO(b/133208730) If we cannot resolve the field, are we then allowed to rename it?
     return getOrCreateInternalState(field).createNewName(field);
   }
 
@@ -80,7 +80,7 @@
     return new FieldNamingState(appView, strategy, reservedNames, internalStatesClone);
   }
 
-  class InternalState implements MemberNamingInternalState, Cloneable {
+  class InternalState implements InternalNamingState, Cloneable {
 
     private int dictionaryIndex;
     private int nextNameIndex;
@@ -98,8 +98,7 @@
       DexString name;
       do {
         name = strategy.next(field, this);
-      } while (reservedNames.isReserved(name, field.type)
-          && !strategy.breakOnNotAvailable(field, name));
+      } while (reservedNames.isReserved(name, field.type));
       return name;
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index a43dcb8..4c751c6 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -289,7 +289,7 @@
 
       for (MethodNamingState<?> namingState : globalStateMap.get(wrapper)) {
         if (!namingState.isReserved(unifiedMethod.name, unifiedMethod.proto)) {
-          namingState.reserveName(unifiedMethod.name, unifiedMethod.proto);
+          namingState.reserveName(unifiedMethod.name, unifiedMethod.proto, unifiedMethod.name);
           changed = true;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/naming/InternalNamingState.java b/src/main/java/com/android/tools/r8/naming/InternalNamingState.java
new file mode 100644
index 0000000..e5749c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/InternalNamingState.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2019, 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;
+
+interface InternalNamingState {
+
+  int getDictionaryIndex();
+
+  int incrementDictionaryIndex();
+
+  int incrementNameIndex(boolean isDirectMethodCall);
+}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index 260b176..f33027b 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
@@ -11,20 +14,17 @@
 
 public interface MemberNamingStrategy {
 
-  DexString next(DexMethod method, MemberNamingInternalState internalState);
+  DexString next(DexMethod method, InternalNamingState internalState);
 
-  DexString next(DexField field, MemberNamingInternalState internalState);
+  DexString next(DexField field, InternalNamingState internalState);
 
-  boolean breakOnNotAvailable(DexReference source, DexString name);
+  DexString getReservedNameOrDefault(
+      DexEncodedMethod method, DexClass holder, DexString defaultValue);
 
-  boolean noObfuscation(DexReference reference);
+  DexString getReservedNameOrDefault(
+      DexEncodedField field, DexClass holder, DexString defaultValue);
 
-  interface MemberNamingInternalState {
+  boolean allowMemberRenaming(DexClass holder);
 
-    int getDictionaryIndex();
-
-    int incrementDictionaryIndex();
-
-    int incrementNameIndex(boolean isDirectMethodCall);
-  }
+  void reportReservationError(DexReference source, DexString name);
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 029f65a..9b07115 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -37,7 +37,8 @@
  * <p>As in the Dalvik VM method dispatch takes argument and return types of methods into account,
  * we can further reuse names if the prototypes of two methods differ. For this, we store the above
  * state separately for each proto using a map from protos to {@link
- * MethodNamingState.InternalState} objects. These internal state objects are also linked.
+ * MethodNamingState.InternalReservationState} objects. These internal state objects are also
+ * linked.
  *
  * <p>Name assignment happens in 4 stages. In the first stage, we record all names that are used by
  * library classes or are flagged using a keep rule as reserved. This step also allocates the {@link
@@ -72,9 +73,9 @@
  * states of classes. Hence, skipping over names during interface naming does not impact their
  * availability in the next phase.
  *
- * <p>In the final stage, we assign names to methods by traversing the subtype tree, now allocating
- * separate naming states for each class starting from the frontier. In the first swoop, we allocate
- * all non-private methods, updating naming states accordingly.
+ * <p>In stage 4, we assign names to methods by traversing the subtype tree, now allocating separate
+ * naming states for each class starting from the frontier. In the first swoop, we allocate all
+ * non-private methods, updating naming states accordingly.
  *
  * <p>Finally, the computed renamings are returned as a map from {@link DexMethod} to {@link
  * DexString}. The MethodNameMinifier object should not be retained to ensure all intermediate state
@@ -139,11 +140,6 @@
     return states.computeIfAbsent(type, f);
   }
 
-  private boolean alwaysReserveMemberNames(DexClass holder) {
-    return !appView.options().getProguardConfiguration().hasApplyMappingFile()
-        && holder.isNotProgramClass();
-  }
-
   private Function<DexProto, ?> getKeyTransform() {
     if (appView.options().getProguardConfiguration().isOverloadAggressively()) {
       // Use the full proto as key, hence reuse names based on full signature.
@@ -184,10 +180,12 @@
     InterfaceMethodNameMinifier interfaceMethodNameMinifier =
         new InterfaceMethodNameMinifier(
             appView, desugaredCallSites, equivalence, frontierState, minifierState);
+    timing.end();
+    timing.begin("Phase 3");
     interfaceMethodNameMinifier.assignNamesToInterfaceMethods(timing, interfaces);
     timing.end();
-    // Phase 3: Assign names top-down by traversing the subtype hierarchy.
-    timing.begin("Phase 3");
+    // Phase 4: Assign names top-down by traversing the subtype hierarchy.
+    timing.begin("Phase 4");
     assignNamesToClassesMethods(appView.dexItemFactory().objectType);
     timing.end();
 
@@ -207,31 +205,42 @@
     //
     // A simple way to ensure this is to process virtual methods first and then direct methods.
     DexClass holder = appView.definitionFor(type);
-    boolean shouldAssignName = holder != null && !alwaysReserveMemberNames(holder);
+    boolean shouldAssignName = holder != null && strategy.allowMemberRenaming(holder);
     if (shouldAssignName) {
       MethodNamingState<?> state =
           computeStateIfAbsent(type, k -> minifierState.getState(holder.superType).createChild());
       for (DexEncodedMethod method : holder.virtualMethodsSorted()) {
-        assignNameToMethod(method, state);
+        assignNameToMethod(method, state, holder);
       }
       for (DexEncodedMethod method : holder.directMethodsSorted()) {
-        assignNameToMethod(method, state);
+        assignNameToMethod(method, state, holder);
       }
     }
-    appView.appInfo().forAllExtendsSubtypes(type, subtype -> assignNamesToClassesMethods(subtype));
+
+    appView.appInfo().forAllExtendsSubtypes(type, this::assignNamesToClassesMethods);
   }
 
-  private void assignNameToMethod(DexEncodedMethod encodedMethod, MethodNamingState<?> state) {
+  private void assignNameToMethod(
+      DexEncodedMethod encodedMethod, MethodNamingState<?> state, DexClass holder) {
     if (encodedMethod.accessFlags.isConstructor()) {
       return;
     }
     DexMethod method = encodedMethod.method;
-    if (!state.isReserved(method.name, method.proto)) {
-      DexString renamedName = state.assignNewNameFor(method, method.name, method.proto);
-      renaming.put(method, renamedName);
-      if (!encodedMethod.accessFlags.isPrivate()) {
-        state.addRenaming(method.name, method.proto, renamedName);
-      }
+    DexString reservedName = strategy.getReservedNameOrDefault(encodedMethod, holder, method.name);
+    if (state.isReserved(reservedName, method.proto)) {
+      return;
+    }
+    DexString newName = state.assignNewNameFor(method, method.name, method.proto);
+    if (newName != method.name) {
+      addRenaming(encodedMethod, state, newName);
+    }
+  }
+
+  private void addRenaming(
+      DexEncodedMethod encodedMethod, MethodNamingState<?> state, DexString renamedName) {
+    renaming.put(encodedMethod.method, renamedName);
+    if (!encodedMethod.accessFlags.isPrivate()) {
+      state.addRenaming(encodedMethod.method.name, encodedMethod.method.proto, renamedName);
     }
   }
 
@@ -276,14 +285,38 @@
 
       DexClass holder = appView.definitionFor(type);
       if (holder != null) {
-        boolean keepAll = alwaysReserveMemberNames(holder) || holder.accessFlags.isAnnotation();
         for (DexEncodedMethod method : shuffleMethods(holder.methods(), appView.options())) {
-          // TODO(christofferqa): Wouldn't it be sufficient only to reserve names for non-private
-          //  methods?
-          if (keepAll
-              || method.accessFlags.isConstructor()
-              || strategy.noObfuscation(method.method)) {
-            reserveNamesForMethod(method.method, state);
+          DexString reservedName = strategy.getReservedNameOrDefault(method, holder, null);
+          if (reservedName != null) {
+            // If the mapping is incorrect, we might try to map a method to an existing name.
+            // class A {
+            //   int m1(String foo) { ... }
+            // }
+            //
+            // class B extends A {
+            //   int m2(String bar) { ... }
+            // }
+            //
+            // and the following reservations (mapping):
+            // A -> a:
+            //   int m1(String foo) -> a
+            // B -> b:
+            //   int m2(String foo) -> a <- ERROR
+            //
+            // In the example, the mapping file specifies that A.m1() should become a.a() and that
+            // B.m2() should become b.a(). This should not be allowed, since b.a() now overrides
+            // a.a(), unlike in the original program.
+            DexString previouslyReservedOriginalName =
+                state.getReservedOriginalName(reservedName, method.method.proto);
+            if (previouslyReservedOriginalName != null
+                && previouslyReservedOriginalName != method.method.name) {
+              strategy.reportReservationError(method.method, reservedName);
+            }
+            state.reserveName(reservedName, method.method.proto, method.method.name);
+            globalState.reserveName(reservedName, method.method.proto, method.method.name);
+            if (reservedName != method.method.name) {
+              addRenaming(method, state, reservedName);
+            }
           }
         }
       }
@@ -291,11 +324,6 @@
       return state;
     }
 
-    private void reserveNamesForMethod(DexMethod method, MethodNamingState<?> state) {
-      state.reserveName(method.name, method.proto);
-      globalState.reserveName(method.name, method.proto);
-    }
-
     public DexType get(DexType type) {
       return frontiers.getOrDefault(type, type);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index 7b0c5fb..adbc1c3 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -7,19 +7,16 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.naming.MemberNamingStrategy.MemberNamingInternalState;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.Sets;
 import java.io.PrintStream;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
 import java.util.function.Function;
 
 class MethodNamingState<KeyType> {
 
   private final MethodNamingState<KeyType> parent;
-  private final Map<KeyType, InternalState> usedNames = new HashMap<>();
+  private final Map<KeyType, InternalReservationState> usedNames = new HashMap<>();
+  private final Map<KeyType, InternalNewNameState> newNameStates = new HashMap<>();
   private final Function<DexProto, KeyType> keyTransform;
   private final MemberNamingStrategy strategy;
 
@@ -37,74 +34,104 @@
     this.strategy = strategy;
   }
 
-  public MethodNamingState<KeyType> createChild() {
+  MethodNamingState<KeyType> createChild() {
     return new MethodNamingState<>(this, keyTransform, strategy);
   }
 
-  private InternalState findInternalStateFor(KeyType key) {
-    InternalState result = usedNames.get(key);
+  private InternalReservationState findInternalReservationStateFor(KeyType key) {
+    InternalReservationState result = usedNames.get(key);
     if (result == null && parent != null) {
-      result = parent.findInternalStateFor(key);
+      result = parent.findInternalReservationStateFor(key);
     }
     return result;
   }
 
-  private InternalState getOrCreateInternalStateFor(KeyType key) {
-    // TODO(herhut): Maybe allocate these sparsely and search via state chain.
-    InternalState result = usedNames.get(key);
+  private InternalReservationState getOrCreateInternalReservationStateFor(KeyType key) {
+    InternalReservationState result = usedNames.get(key);
     if (result == null) {
-      InternalState parentState = parent != null ? parent.getOrCreateInternalStateFor(key) : null;
-      result = new InternalState(parentState);
+      InternalReservationState parentState =
+          parent != null ? parent.getOrCreateInternalReservationStateFor(key) : null;
+      result = new InternalReservationState(parentState);
       usedNames.put(key, result);
     }
     return result;
   }
 
+  private InternalNewNameState findInternalNewNameStateFor(KeyType key) {
+    InternalNewNameState result = newNameStates.get(key);
+    if (result == null && parent != null) {
+      result = parent.findInternalNewNameStateFor(key);
+    }
+    return result;
+  }
+
+  private InternalNewNameState getOrCreateNewNameStateFor(KeyType key) {
+    InternalNewNameState result = newNameStates.get(key);
+    if (result == null) {
+      InternalReservationState reservationState = getOrCreateInternalReservationStateFor(key);
+      assert reservationState != null;
+      InternalNewNameState parentState =
+          parent != null ? parent.getOrCreateNewNameStateFor(key) : null;
+      result = new InternalNewNameState(parentState, reservationState);
+      newNameStates.put(key, result);
+    }
+    return result;
+  }
+
   private DexString getAssignedNameFor(DexString name, KeyType key) {
-    InternalState state = findInternalStateFor(key);
+    InternalReservationState state = findInternalReservationStateFor(key);
     if (state == null) {
       return null;
     }
     return state.getAssignedNameFor(name);
   }
 
-  public DexString assignNewNameFor(DexMethod source, DexString original, DexProto proto) {
+  DexString assignNewNameFor(DexMethod source, DexString original, DexProto proto) {
     KeyType key = keyTransform.apply(proto);
     DexString result = getAssignedNameFor(original, key);
     if (result == null) {
-      InternalState state = getOrCreateInternalStateFor(key);
+      InternalNewNameState state = getOrCreateNewNameStateFor(key);
       result = state.getNewNameFor(source);
     }
     return result;
   }
 
-  public void reserveName(DexString name, DexProto proto) {
+  void reserveName(DexString name, DexProto proto, DexString originalName) {
     KeyType key = keyTransform.apply(proto);
-    InternalState state = getOrCreateInternalStateFor(key);
-    state.reserveName(name);
+    InternalReservationState state = getOrCreateInternalReservationStateFor(key);
+    state.reserveName(name, originalName);
   }
 
-  public boolean isReserved(DexString name, DexProto proto) {
+  boolean isReserved(DexString name, DexProto proto) {
     KeyType key = keyTransform.apply(proto);
-    InternalState state = findInternalStateFor(key);
+    InternalReservationState state = findInternalReservationStateFor(key);
     if (state == null) {
       return false;
     }
     return state.isReserved(name);
   }
 
-  public boolean isAvailable(DexProto proto, DexString candidate) {
+  DexString getReservedOriginalName(DexString name, DexProto proto) {
     KeyType key = keyTransform.apply(proto);
-    InternalState state = findInternalStateFor(key);
+    InternalReservationState state = findInternalReservationStateFor(key);
+    if (state == null) {
+      return null;
+    }
+    return state.getReservedOriginalName(name);
+  }
+
+  boolean isAvailable(DexProto proto, DexString candidate) {
+    KeyType key = keyTransform.apply(proto);
+    InternalReservationState state = findInternalReservationStateFor(key);
     if (state == null) {
       return true;
     }
     return state.isAvailable(candidate);
   }
 
-  public void addRenaming(DexString original, DexProto proto, DexString newName) {
+  void addRenaming(DexString original, DexProto proto, DexString newName) {
     KeyType key = keyTransform.apply(proto);
-    InternalState state = getOrCreateInternalStateFor(key);
+    InternalReservationState state = getOrCreateInternalReservationStateFor(key);
     state.addRenaming(original, newName);
   }
 
@@ -114,7 +141,7 @@
       String indentation,
       PrintStream out) {
     KeyType key = keyTransform.apply(proto);
-    InternalState state = getOrCreateInternalStateFor(key);
+    InternalNewNameState state = getOrCreateNewNameStateFor(key);
     out.print(indentation);
     out.print("NamingState(node=`");
     out.print(stateKeyGetter.apply(this).toSourceString());
@@ -131,76 +158,29 @@
     }
   }
 
-  class InternalState implements MemberNamingInternalState {
-
-    private static final int INITIAL_NAME_COUNT = 1;
-    private static final int INITIAL_DICTIONARY_INDEX = 0;
-
-    private final InternalState parentInternalState;
-    private Set<DexString> reservedNames = null;
+  class InternalReservationState {
+    private final InternalReservationState parentInternalState;
+    private Map<DexString, DexString> reservedNames = null;
     private Map<DexString, DexString> renamings = null;
-    private int virtualNameCount;
-    private int directNameCount = 0;
-    private int dictionaryIndex;
 
-    private InternalState(InternalState parentInternalState) {
+    private InternalReservationState(InternalReservationState parentInternalState) {
       this.parentInternalState = parentInternalState;
-      this.dictionaryIndex =
-          parentInternalState == null
-              ? INITIAL_DICTIONARY_INDEX
-              : parentInternalState.dictionaryIndex;
-      this.virtualNameCount =
-          parentInternalState == null ? INITIAL_NAME_COUNT : parentInternalState.virtualNameCount;
     }
 
-    private boolean isReserved(DexString name) {
-      return (reservedNames != null && reservedNames.contains(name))
+    boolean isReserved(DexString name) {
+      return (reservedNames != null && reservedNames.containsKey(name))
           || (parentInternalState != null && parentInternalState.isReserved(name));
     }
 
-    private boolean isAvailable(DexString name) {
-      return !(renamings != null && renamings.containsValue(name))
-          && !(reservedNames != null && reservedNames.contains(name))
-          && (parentInternalState == null || parentInternalState.isAvailable(name));
-    }
-
-    void reserveName(DexString name) {
-      if (reservedNames == null) {
-        reservedNames = Sets.newIdentityHashSet();
+    DexString getReservedOriginalName(DexString name) {
+      DexString result = null;
+      if (reservedNames != null) {
+        result = reservedNames.get(name);
       }
-      reservedNames.add(name);
-    }
-
-    @Override
-    public int getDictionaryIndex() {
-      return dictionaryIndex;
-    }
-
-    @Override
-    public int incrementDictionaryIndex() {
-      return dictionaryIndex++;
-    }
-
-    private boolean checkParentPublicNameCountIsLessThanOrEqual() {
-      int maxParentCount = 0;
-      InternalState tmp = parentInternalState;
-      while (tmp != null) {
-        maxParentCount = Math.max(tmp.virtualNameCount, maxParentCount);
-        tmp = tmp.parentInternalState;
+      if (result == null && parentInternalState != null) {
+        result = parentInternalState.getReservedOriginalName(name);
       }
-      assert maxParentCount <= virtualNameCount;
-      return true;
-    }
-
-    @Override
-    public int incrementNameIndex(boolean isDirectMethodCall) {
-      assert checkParentPublicNameCountIsLessThanOrEqual();
-      if (isDirectMethodCall) {
-        return virtualNameCount + directNameCount++;
-      } else {
-        assert directNameCount == 0;
-        return virtualNameCount++;
-      }
+      return result;
     }
 
     DexString getAssignedNameFor(DexString original) {
@@ -214,12 +194,17 @@
       return result;
     }
 
-    private DexString getNewNameFor(DexMethod source) {
-      DexString name;
-      do {
-        name = strategy.next(source, this);
-      } while (!isAvailable(name) && !strategy.breakOnNotAvailable(source, name));
-      return name;
+    private boolean isAvailable(DexString name) {
+      return !(renamings != null && renamings.containsValue(name))
+          && !(reservedNames != null && reservedNames.containsKey(name))
+          && (parentInternalState == null || parentInternalState.isAvailable(name));
+    }
+
+    void reserveName(DexString name, DexString originalName) {
+      if (reservedNames == null) {
+        reservedNames = new HashMap<>();
+      }
+      reservedNames.put(name, originalName);
     }
 
     void addRenaming(DexString original, DexString newName) {
@@ -229,58 +214,17 @@
       renamings.put(original, newName);
     }
 
-    void printInternalState(
-        MethodNamingState<?> expectedNamingState,
-        Function<MethodNamingState<?>, DexType> stateKeyGetter,
-        String indentation,
-        PrintStream out) {
-      assert expectedNamingState == MethodNamingState.this;
-
-      DexType stateKey = stateKeyGetter.apply(expectedNamingState);
-      out.print(indentation);
-      out.print("InternalState(node=`");
-      out.print(stateKey != null ? stateKey.toSourceString() : "<GLOBAL>");
-      out.println("`)");
-
-      printLastName(indentation + "  ", out);
-      printReservedNames(indentation + "  ", out);
-      printRenamings(indentation + "  ", out);
-
-      if (parentInternalState != null) {
-        parentInternalState.printInternalState(
-            expectedNamingState.parent, stateKeyGetter, indentation + "  ", out);
-      }
-    }
-
-    void printLastName(String indentation, PrintStream out) {
-      out.print(indentation);
-      out.print("Last name: ");
-      int index = virtualNameCount + directNameCount;
-      if (index > 1) {
-        out.print(StringUtils.numberToIdentifier(index - 1));
-        out.print(" (public name count: ");
-        out.print(virtualNameCount);
-        out.print(")");
-        out.print(" (direct name count: ");
-        out.print(directNameCount);
-        out.print(")");
-      } else {
-        out.print("<NONE>");
-      }
-      out.println();
-    }
-
     void printReservedNames(String indentation, PrintStream out) {
       out.print(indentation);
       out.print("Reserved names:");
       if (reservedNames == null || reservedNames.isEmpty()) {
         out.print(" <NO RESERVED NAMES>");
       } else {
-        for (DexString reservedName : reservedNames) {
+        for (DexString reservedName : reservedNames.keySet()) {
           out.print(System.lineSeparator());
           out.print(indentation);
           out.print("  ");
-          out.print(reservedName.toSourceString());
+          out.print(reservedName.toSourceString() + "(by " + reservedNames.get(reservedName) + ")");
         }
       }
       out.println();
@@ -304,4 +248,102 @@
       out.println();
     }
   }
+
+  class InternalNewNameState implements InternalNamingState {
+
+    private final InternalNewNameState parentInternalState;
+    private final InternalReservationState reservationState;
+
+    private static final int INITIAL_NAME_COUNT = 1;
+    private static final int INITIAL_DICTIONARY_INDEX = 0;
+
+    private int virtualNameCount;
+    private int directNameCount = 0;
+    private int dictionaryIndex;
+
+    private InternalNewNameState(
+        InternalNewNameState parentInternalState, InternalReservationState reservationState) {
+      this.parentInternalState = parentInternalState;
+      this.reservationState = reservationState;
+      this.dictionaryIndex =
+          parentInternalState == null
+              ? INITIAL_DICTIONARY_INDEX
+              : parentInternalState.dictionaryIndex;
+      this.virtualNameCount =
+          parentInternalState == null ? INITIAL_NAME_COUNT : parentInternalState.virtualNameCount;
+      assert reservationState != null;
+    }
+
+    @Override
+    public int getDictionaryIndex() {
+      return dictionaryIndex;
+    }
+
+    @Override
+    public int incrementDictionaryIndex() {
+      return dictionaryIndex++;
+    }
+
+    private boolean checkParentPublicNameCountIsLessThanOrEqual() {
+      int maxParentCount = 0;
+      InternalNewNameState tmp = parentInternalState;
+      while (tmp != null) {
+        maxParentCount = Math.max(tmp.virtualNameCount, maxParentCount);
+        tmp = tmp.parentInternalState;
+      }
+      assert maxParentCount <= virtualNameCount;
+      return true;
+    }
+
+    @Override
+    public int incrementNameIndex(boolean isDirectMethodCall) {
+      assert checkParentPublicNameCountIsLessThanOrEqual();
+      if (isDirectMethodCall) {
+        return virtualNameCount + directNameCount++;
+      } else {
+        assert directNameCount == 0;
+        return virtualNameCount++;
+      }
+    }
+
+    private DexString getNewNameFor(DexMethod source) {
+      DexString name;
+      do {
+        name = strategy.next(source, this);
+      } while (!reservationState.isAvailable(name));
+      return name;
+    }
+
+    void printInternalState(
+        MethodNamingState<?> expectedNamingState,
+        Function<MethodNamingState<?>, DexType> stateKeyGetter,
+        String indentation,
+        PrintStream out) {
+      assert expectedNamingState == MethodNamingState.this;
+
+      DexType stateKey = stateKeyGetter.apply(expectedNamingState);
+      out.print(indentation);
+      out.print("InternalState(node=`");
+      out.print(stateKey != null ? stateKey.toSourceString() : "<GLOBAL>");
+      out.println("`)");
+
+      printLastName(indentation + "  ", out);
+      reservationState.printReservedNames(indentation + "  ", out);
+      reservationState.printRenamings(indentation + "  ", out);
+
+      if (parentInternalState != null) {
+        parentInternalState.printInternalState(
+            expectedNamingState.parent, stateKeyGetter, indentation + "  ", out);
+      }
+    }
+
+    void printLastName(String indentation, PrintStream out) {
+      out.print(indentation);
+      out.print("public name count: ");
+      out.print(virtualNameCount);
+      out.print(", ");
+      out.print("direct name count: ");
+      out.println(directNameCount);
+    }
+  }
 }
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 4bdf87c..6906a06 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,9 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -15,15 +18,13 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
-import com.android.tools.r8.naming.ClassNameMinifier.Namespace;
 import com.android.tools.r8.naming.ClassNameMinifier.PackageNamingStrategy;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
 import com.android.tools.r8.utils.Timing;
-import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -50,7 +51,7 @@
         new ClassNameMinifier(
             appView,
             new MinificationClassNamingStrategy(appView),
-            new MinificationPackageNamingStrategy(),
+            new MinificationPackageNamingStrategy(appView),
             // Use deterministic class order to make sure renaming is deterministic.
             appView.appInfo().classesWithDeterministicOrder());
     ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
@@ -84,29 +85,57 @@
     return lens;
   }
 
-  static class MinificationClassNamingStrategy implements ClassNamingStrategy {
+  abstract static class BaseMinificationNamingStrategy {
 
-    private final AppView<?> appView;
-    private final Object2IntMap<Namespace> namespaceCounters = new Object2IntLinkedOpenHashMap<>();
+    // We have to ensure that the names proposed by the minifier is not used in the obfuscation
+    // dictionary. We use a list for direct indexing based on a number and a set for looking up.
+    private final List<String> obfuscationDictionary;
+    private final Set<String> obfuscationDictionaryForLookup;
+    private final MixedCasing mixedCasing;
+
+    BaseMinificationNamingStrategy(List<String> obfuscationDictionary, boolean dontUseMixedCasing) {
+      this.obfuscationDictionary = obfuscationDictionary;
+      this.obfuscationDictionaryForLookup = new HashSet<>(this.obfuscationDictionary);
+      this.mixedCasing =
+          dontUseMixedCasing ? MixedCasing.DONT_USE_MIXED_CASE : MixedCasing.USE_MIXED_CASE;
+      assert obfuscationDictionary != null;
+    }
+
+    String nextName(char[] packagePrefix, InternalNamingState state, boolean isDirectMethodCall) {
+      StringBuilder nextName = new StringBuilder();
+      nextName.append(packagePrefix);
+      if (state.getDictionaryIndex() < obfuscationDictionary.size()) {
+        nextName.append(obfuscationDictionary.get(state.incrementDictionaryIndex()));
+      } else {
+        String nextString;
+        do {
+          nextString =
+              SymbolGenerationUtils.numberToIdentifier(
+                  state.incrementNameIndex(isDirectMethodCall), mixedCasing);
+        } while (obfuscationDictionaryForLookup.contains(nextString));
+        nextName.append(nextString);
+      }
+      return nextName.toString();
+    }
+  }
+
+  static class MinificationClassNamingStrategy extends BaseMinificationNamingStrategy
+      implements ClassNamingStrategy {
+
+    final AppView<?> appView;
+    private final DexItemFactory factory;
 
     MinificationClassNamingStrategy(AppView<?> appView) {
+      super(
+          appView.options().getProguardConfiguration().getClassObfuscationDictionary(),
+          appView.options().getProguardConfiguration().hasDontUseMixedCaseClassnames());
       this.appView = appView;
-      namespaceCounters.defaultReturnValue(1);
+      factory = appView.dexItemFactory();
     }
 
     @Override
-    public DexString next(Namespace namespace, DexType type, char[] packagePrefix) {
-      int counter = namespaceCounters.put(namespace, namespaceCounters.getInt(namespace) + 1);
-      DexString string =
-          appView
-              .dexItemFactory()
-              .createString(StringUtils.numberToIdentifier(counter, packagePrefix, true));
-      return string;
-    }
-
-    @Override
-    public boolean bypassDictionary() {
-      return false;
+    public DexString next(DexType type, char[] packagePrefix, InternalNamingState state) {
+      return factory.createString(nextName(packagePrefix, state, false) + ";");
     }
 
     @Override
@@ -115,84 +144,91 @@
     }
   }
 
-  static class MinificationPackageNamingStrategy implements PackageNamingStrategy {
+  static class MinificationPackageNamingStrategy extends BaseMinificationNamingStrategy
+      implements PackageNamingStrategy {
 
-    private final Object2IntMap<Namespace> namespaceCounters = new Object2IntLinkedOpenHashMap<>();
-
-    public MinificationPackageNamingStrategy() {
-      namespaceCounters.defaultReturnValue(1);
+    MinificationPackageNamingStrategy(AppView<?> appView) {
+      super(
+          appView.options().getProguardConfiguration().getPackageObfuscationDictionary(),
+          appView.options().getProguardConfiguration().hasDontUseMixedCaseClassnames());
     }
 
     @Override
-    public String next(Namespace namespace, char[] packagePrefix) {
+    public String next(char[] packagePrefix, InternalNamingState state) {
       // Note that the differences between this method and the other variant for class renaming are
       // 1) this one uses the different dictionary and counter,
       // 2) this one does not append ';' at the end, and
       // 3) this one removes 'L' at the beginning to make the return value a binary form.
-      int counter = namespaceCounters.put(namespace, namespaceCounters.getInt(namespace) + 1);
-      return StringUtils.numberToIdentifier(counter, packagePrefix, false).substring(1);
-    }
-
-    @Override
-    public boolean bypassDictionary() {
-      return false;
+      return nextName(packagePrefix, state, false).substring(1);
     }
   }
 
-  static class MinifierMemberNamingStrategy implements MemberNamingStrategy {
+  static class MinifierMemberNamingStrategy extends BaseMinificationNamingStrategy
+      implements MemberNamingStrategy {
 
+    final AppView<?> appView;
     private final DexItemFactory factory;
-    // We have to ensure that the names proposed by the minifier is not used in the obfuscation
-    // dictionary. We use a list for direct indexing based on a number and a set for looking up.
-    private final List<String> obfuscationDictionary;
-    private final Set<String> obfuscationDictionaryForLookup;
-
-    private final AppView<?> appView;
 
     public MinifierMemberNamingStrategy(AppView<?> appView) {
+      super(appView.options().getProguardConfiguration().getObfuscationDictionary(), false);
       this.appView = appView;
       this.factory = appView.dexItemFactory();
-      this.obfuscationDictionary =
-          appView.options().getProguardConfiguration().getObfuscationDictionary();
-      this.obfuscationDictionaryForLookup = new HashSet<>(this.obfuscationDictionary);
-      assert this.obfuscationDictionary != null;
     }
 
     @Override
-    public DexString next(DexMethod method, MemberNamingInternalState internalState) {
+    public DexString next(DexMethod method, InternalNamingState internalState) {
+      assert checkAllowMemberRenaming(method.holder);
       DexEncodedMethod encodedMethod = appView.definitionFor(method);
       boolean isDirectOrStatic = encodedMethod.isDirectMethod() || encodedMethod.isStatic();
       return getNextName(internalState, isDirectOrStatic);
     }
 
     @Override
-    public DexString next(DexField field, MemberNamingInternalState internalState) {
+    public DexString next(DexField field, InternalNamingState internalState) {
+      assert checkAllowMemberRenaming(field.holder);
       return getNextName(internalState, false);
     }
 
-    private DexString getNextName(
-        MemberNamingInternalState internalState, boolean isDirectOrStatic) {
-      if (internalState.getDictionaryIndex() < obfuscationDictionary.size()) {
-        return factory.createString(
-            obfuscationDictionary.get(internalState.incrementDictionaryIndex()));
-      } else {
-        String nextString;
-        do {
-          int counter = internalState.incrementNameIndex(isDirectOrStatic);
-          nextString = StringUtils.numberToIdentifier(counter);
-        } while (obfuscationDictionaryForLookup.contains(nextString));
-        return factory.createString(nextString);
+    private DexString getNextName(InternalNamingState internalState, boolean isDirectOrStatic) {
+      return factory.createString(nextName(EMPTY_CHAR_ARRAY, internalState, isDirectOrStatic));
+    }
+
+    @Override
+    public DexString getReservedNameOrDefault(
+        DexEncodedMethod method, DexClass holder, DexString defaultValue) {
+      if (!allowMemberRenaming(holder)
+          || holder.accessFlags.isAnnotation()
+          || method.accessFlags.isConstructor()
+          || appView.rootSet().mayNotBeMinified(method.method, appView)) {
+        return method.method.name;
       }
+      return defaultValue;
     }
 
     @Override
-    public boolean breakOnNotAvailable(DexReference source, DexString name) {
-      return false;
+    public DexString getReservedNameOrDefault(
+        DexEncodedField field, DexClass holder, DexString defaultValue) {
+      if (holder.isLibraryClass() || appView.rootSet().mayNotBeMinified(field.field, appView)) {
+        return field.field.name;
+      }
+      return defaultValue;
     }
 
     @Override
-    public boolean noObfuscation(DexReference reference) {
-      return appView.rootSet().mayNotBeMinified(reference, appView);
+    public boolean allowMemberRenaming(DexClass holder) {
+      return holder.isProgramClass();
+    }
+
+    public boolean checkAllowMemberRenaming(DexType holder) {
+      DexClass clazz = appView.definitionFor(holder);
+      assert clazz != null && allowMemberRenaming(clazz);
+      return true;
+    }
+
+    @Override
+    public void reportReservationError(DexReference source, DexString name) {
+      assert false;
+      // This should only happen when applymapping is used and will be caught in that strategy.
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 4c2c8cf..895b8c5 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -14,31 +16,59 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
-import com.android.tools.r8.naming.ClassNameMinifier.Namespace;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
+import com.android.tools.r8.naming.Minifier.MinificationClassNamingStrategy;
 import com.android.tools.r8.naming.Minifier.MinificationPackageNamingStrategy;
+import com.android.tools.r8.naming.Minifier.MinifierMemberNamingStrategy;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 
+/**
+ * The ProguardMapMinifier will assign names to classes and members following the initial naming
+ * seed given by the mapping files.
+ *
+ * <p>First the object hierarchy is traversed maintaining a collection of all program classes and
+ * classes that needs to be renamed in {@link #mappedClasses}. For each level we keep track of all
+ * renamed members and propagate all non-private items to descendants. This is necessary to ensure
+ * that virtual methods are renamed when there are "gaps" in the hierarchy. We keep track of all
+ * namings such that future renaming of non-private members will not collide or fail with an error.
+ *
+ * <p>Second, we compute desugared default interface methods and companion classes to ensure these
+ * can be referred to by clients.
+ *
+ * <p>Third, we traverse all reachable interfaces for class mappings and add them to our tracking
+ * maps. Otherwise, the minification follows the ordinary minification.
+ */
 public class ProguardMapMinifier {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final SeedMapper seedMapper;
   private final Set<DexCallSite> desugaredCallSites;
+  private final BiMap<DexType, DexString> mappedNames = HashBiMap.create();
+  private final List<DexClass> mappedClasses = new ArrayList<>();
+  private final Map<DexReference, MemberNaming> memberNames = new IdentityHashMap<>();
+  private final Map<DexType, DexString> syntheticCompanionClasses = new IdentityHashMap<>();
+  private final Map<DexMethod, DexString> defaultInterfaceMethodImplementationNames =
+      new IdentityHashMap<>();
+  private final Map<DexMethod, DexString> additionalMethodNamings = new IdentityHashMap<>();
+  private final Map<DexField, DexString> additionalFieldNamings = new IdentityHashMap<>();
 
   public ProguardMapMinifier(
       AppView<AppInfoWithLiveness> appView,
@@ -50,101 +80,63 @@
   }
 
   public NamingLens run(Timing timing) {
-    timing.begin("mapping classes");
+    timing.begin("MappingClasses");
+    computeMapping(appView.dexItemFactory().objectType, new ArrayDeque<>());
+    timing.end();
 
-    Map<DexType, DexString> mappedNames = new IdentityHashMap<>();
-    List<DexClass> mappedClasses = new ArrayList<>();
-    Map<DexReference, MemberNaming> memberNames = new IdentityHashMap<>();
-    Map<DexType, DexString> syntheticCompanionClasses = new IdentityHashMap<>();
-    Map<DexMethod, DexString> defaultInterfaceMethodImplementationNames = new IdentityHashMap<>();
-    for (String key : seedMapper.getKeyset()) {
-      ClassNamingForMapApplier classNaming = seedMapper.getMapping(key);
-      DexType type =
-          appView.dexItemFactory().lookupType(appView.dexItemFactory().createString(key));
-      if (type == null) {
-        // The map contains additional mapping of classes compared to what we have seen. This should
-        // have no effect.
-        continue;
+    timing.begin("MappingDefaultInterfaceMethods");
+    computeDefaultInterfaceMethodMethods();
+    timing.end();
+
+    timing.begin("ComputeInterfaces");
+    // We have to compute interfaces
+    Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
+    for (DexClass dexClass : appView.appInfo().computeReachableInterfaces(desugaredCallSites)) {
+      ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(dexClass.type);
+      if (classNaming != null) {
+        DexString mappedName = appView.dexItemFactory().createString(classNaming.renamedName);
+        checkAndAddMappedNames(dexClass.type, mappedName, classNaming.position);
       }
-      DexClass dexClass = appView.definitionFor(type);
-      if (dexClass == null) {
-        computeDefaultInterfaceMethodMappings(
-            type,
-            classNaming,
-            syntheticCompanionClasses,
-            defaultInterfaceMethodImplementationNames);
-        continue;
-      }
-      DexString mappedName = appView.dexItemFactory().createString(classNaming.renamedName);
-      DexType mappedType = appView.dexItemFactory().lookupType(mappedName);
-      // The mappedType has to be available:
-      // - If it is null we have not seen it.
-      // - If the mapped type is itself the name is already reserved (by itself).
-      // - If the there is no definition for the mapped type we will not get a naming clash.
-      // Otherwise, there will be a naming conflict.
-      if (mappedType != null && type != mappedType && appView.definitionFor(mappedType) != null) {
-        appView
-            .options()
-            .reporter
-            .error(
-                ApplyMappingError.mapToExistingClass(
-                    type.toString(), mappedType.toString(), classNaming.position));
-      }
-      mappedNames.put(type, mappedName);
       mappedClasses.add(dexClass);
-      classNaming.forAllMethodNaming(
-          memberNaming -> {
-            Signature signature = memberNaming.getOriginalSignature();
-            assert !signature.isQualified();
-            DexMethod originalMethod =
-                ((MethodSignature) signature).toDexMethod(appView.dexItemFactory(), type);
-            assert !memberNames.containsKey(originalMethod);
-            memberNames.put(originalMethod, memberNaming);
-          });
-      classNaming.forAllFieldNaming(
-          memberNaming -> {
-            Signature signature = memberNaming.getOriginalSignature();
-            assert !signature.isQualified();
-            DexField originalField =
-                ((FieldSignature) signature).toDexField(appView.dexItemFactory(), type);
-            assert !memberNames.containsKey(originalField);
-            memberNames.put(originalField, memberNaming);
-          });
+      interfaces.add(dexClass);
     }
+    timing.end();
 
     appView.options().reporter.failIfPendingErrors();
 
+    // To keep the order deterministic, we sort the classes by their type, which is a unique key.
+    mappedClasses.sort((a, b) -> a.type.slowCompareTo(b.type));
+
+    timing.begin("MinifyClasses");
     ClassNameMinifier classNameMinifier =
         new ClassNameMinifier(
             appView,
-            new ApplyMappingClassNamingStrategy(mappedNames),
+            new ApplyMappingClassNamingStrategy(appView, mappedNames),
             // The package naming strategy will actually not be used since all classes and methods
             // will be output with identity name if not found in mapping. However, there is a check
             // in the ClassNameMinifier that the strategy should produce a "fresh" name so we just
             // use the existing strategy.
-            new MinificationPackageNamingStrategy(),
+            new MinificationPackageNamingStrategy(appView),
             mappedClasses);
     ClassRenaming classRenaming =
         classNameMinifier.computeRenaming(timing, syntheticCompanionClasses);
     timing.end();
 
-    Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
-    interfaces.addAll(appView.appInfo().computeReachableInterfaces(desugaredCallSites));
-
     ApplyMappingMemberNamingStrategy nameStrategy =
-        new ApplyMappingMemberNamingStrategy(
-            memberNames, appView.dexItemFactory(), appView.options().reporter);
+        new ApplyMappingMemberNamingStrategy(appView, memberNames);
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
         new MethodNameMinifier(appView, nameStrategy)
             .computeRenaming(interfaces, desugaredCallSites, timing);
     // Amend the method renamings with the default interface methods.
     methodRenaming.renaming.putAll(defaultInterfaceMethodImplementationNames);
+    methodRenaming.renaming.putAll(additionalMethodNamings);
     timing.end();
 
     timing.begin("MinifyFields");
     FieldRenaming fieldRenaming =
         new FieldNameMinifier(appView, nameStrategy).computeRenaming(interfaces, timing);
+    fieldRenaming.renaming.putAll(additionalFieldNamings);
     timing.end();
 
     appView.options().reporter.failIfPendingErrors();
@@ -158,7 +150,142 @@
     return lens;
   }
 
-  private void computeDefaultInterfaceMethodMappings(
+  private void computeMapping(DexType type, Deque<Map<DexReference, MemberNaming>> buildUpNames) {
+    ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
+    DexClass dexClass = appView.definitionFor(type);
+
+    // Keep track of classes that needs to get renamed.
+    if (dexClass != null && (classNaming != null || dexClass.isProgramClass())) {
+      mappedClasses.add(dexClass);
+    }
+
+    Map<DexReference, MemberNaming> nonPrivateMembers = new IdentityHashMap<>();
+
+    if (classNaming != null) {
+      // TODO(b/133091438) assert that !dexClass.isLibaryClass();
+      DexString mappedName = appView.dexItemFactory().createString(classNaming.renamedName);
+      checkAndAddMappedNames(type, mappedName, classNaming.position);
+
+      classNaming.forAllMethodNaming(
+          memberNaming -> {
+            Signature signature = memberNaming.getOriginalSignature();
+            assert !signature.isQualified();
+            DexMethod originalMethod =
+                ((MethodSignature) signature).toDexMethod(appView.dexItemFactory(), type);
+            assert !memberNames.containsKey(originalMethod);
+            memberNames.put(originalMethod, memberNaming);
+            DexEncodedMethod encodedMethod = appView.definitionFor(originalMethod);
+            if (encodedMethod == null || !encodedMethod.accessFlags.isPrivate()) {
+              nonPrivateMembers.put(originalMethod, memberNaming);
+            }
+          });
+      classNaming.forAllFieldNaming(
+          memberNaming -> {
+            Signature signature = memberNaming.getOriginalSignature();
+            assert !signature.isQualified();
+            DexField originalField =
+                ((FieldSignature) signature).toDexField(appView.dexItemFactory(), type);
+            assert !memberNames.containsKey(originalField);
+            memberNames.put(originalField, memberNaming);
+            DexEncodedField encodedField = appView.definitionFor(originalField);
+            if (encodedField == null || !encodedField.accessFlags.isPrivate()) {
+              nonPrivateMembers.put(originalField, memberNaming);
+            }
+          });
+    } else {
+      // We have to ensure we do not rename to an existing member, that cannot be renamed.
+      if (dexClass == null || !appView.options().isMinifying()) {
+        checkAndAddMappedNames(type, type.descriptor, Position.UNKNOWN);
+      } else if (appView.options().isMinifying()
+          && appView.rootSet().mayNotBeMinified(type, appView)) {
+        checkAndAddMappedNames(type, type.descriptor, Position.UNKNOWN);
+      }
+    }
+
+    for (Map<DexReference, MemberNaming> parentMembers : buildUpNames) {
+      for (DexReference key : parentMembers.keySet()) {
+        if (key.isDexMethod()) {
+          DexMethod parentReference = key.asDexMethod();
+          DexMethod parentReferenceOnCurrentType =
+              appView
+                  .dexItemFactory()
+                  .createMethod(type, parentReference.proto, parentReference.name);
+          addMemberNaming(
+              key, parentReferenceOnCurrentType, parentMembers, additionalMethodNamings);
+        } else {
+          DexField parentReference = key.asDexField();
+          DexField parentReferenceOnCurrentType =
+              appView
+                  .dexItemFactory()
+                  .createField(type, parentReference.type, parentReference.name);
+          addMemberNaming(key, parentReferenceOnCurrentType, parentMembers, additionalFieldNamings);
+        }
+      }
+    }
+
+    if (nonPrivateMembers.size() > 0) {
+      buildUpNames.addLast(nonPrivateMembers);
+      appView
+          .appInfo()
+          .forAllExtendsSubtypes(type, subType -> computeMapping(subType, buildUpNames));
+      buildUpNames.removeLast();
+    } else {
+      appView
+          .appInfo()
+          .forAllExtendsSubtypes(type, subType -> computeMapping(subType, buildUpNames));
+    }
+  }
+
+  private <T extends DexReference> void addMemberNaming(
+      DexReference key,
+      T member,
+      Map<DexReference, MemberNaming> parentMembers,
+      Map<T, DexString> additionalMemberNamings) {
+    // We might have overridden a naming in the direct class namings above.
+    if (!memberNames.containsKey(member)) {
+      DexString renamedName =
+          appView.dexItemFactory().createString(parentMembers.get(key).getRenamedName());
+      memberNames.put(member, parentMembers.get(key));
+      additionalMemberNamings.put(member, renamedName);
+    }
+  }
+
+  private void checkAndAddMappedNames(DexType type, DexString mappedName, Position position) {
+    if (mappedNames.inverse().containsKey(mappedName)) {
+      appView
+          .options()
+          .reporter
+          .error(
+              ApplyMappingError.mapToExistingClass(
+                  type.toString(), mappedName.toString(), position));
+    } else {
+      mappedNames.put(type, mappedName);
+    }
+  }
+
+  private void computeDefaultInterfaceMethodMethods() {
+    for (String key : seedMapper.getKeyset()) {
+      ClassNamingForMapApplier classNaming = seedMapper.getMapping(key);
+      DexType type =
+          appView.dexItemFactory().lookupType(appView.dexItemFactory().createString(key));
+      if (type == null) {
+        // The map contains additional mapping of classes compared to what we have seen. This should
+        // have no effect.
+        continue;
+      }
+      DexClass dexClass = appView.definitionFor(type);
+      if (dexClass == null) {
+        computeDefaultInterfaceMethodMappingsForType(
+            type,
+            classNaming,
+            syntheticCompanionClasses,
+            defaultInterfaceMethodImplementationNames);
+        continue;
+      }
+    }
+  }
+
+  private void computeDefaultInterfaceMethodMappingsForType(
       DexType type,
       ClassNamingForMapApplier classNaming,
       Map<DexType, DexString> syntheticCompanionClasses,
@@ -196,88 +323,110 @@
     }
   }
 
-  static class ApplyMappingClassNamingStrategy implements ClassNamingStrategy {
+  static class ApplyMappingClassNamingStrategy extends MinificationClassNamingStrategy {
 
     private final Map<DexType, DexString> mappings;
+    private final boolean isMinifying;
 
-    ApplyMappingClassNamingStrategy(Map<DexType, DexString> mappings) {
+    ApplyMappingClassNamingStrategy(AppView<?> appView, Map<DexType, DexString> mappings) {
+      super(appView);
       this.mappings = mappings;
+      this.isMinifying = appView.options().isMinifying();
     }
 
     @Override
-    public DexString next(Namespace namespace, DexType type, char[] packagePrefix) {
-      return mappings.getOrDefault(type, type.descriptor);
-    }
-
-    @Override
-    public boolean bypassDictionary() {
-      return true;
+    public DexString next(DexType type, char[] packagePrefix, InternalNamingState state) {
+      DexString nextName = mappings.get(type);
+      if (nextName != null) {
+        return nextName;
+      }
+      assert !(isMinifying && noObfuscation(type));
+      return isMinifying ? super.next(type, packagePrefix, state) : type.descriptor;
     }
 
     @Override
     public boolean noObfuscation(DexType type) {
-      // We have an explicit mapping from the proguard map thus everything might have to be renamed.
-      return false;
+      if (mappings.containsKey(type)) {
+        return false;
+      }
+      DexClass dexClass = appView.definitionFor(type);
+      if (dexClass == null || dexClass.isNotProgramClass()) {
+        return true;
+      }
+      return super.noObfuscation(type);
     }
   }
 
-  static class ApplyMappingMemberNamingStrategy implements MemberNamingStrategy {
+  static class ApplyMappingMemberNamingStrategy extends MinifierMemberNamingStrategy {
 
     private final Map<DexReference, MemberNaming> mappedNames;
     private final DexItemFactory factory;
     private final Reporter reporter;
 
     public ApplyMappingMemberNamingStrategy(
-        Map<DexReference, MemberNaming> mappedNames, DexItemFactory factory, Reporter reporter) {
+        AppView<?> appView, Map<DexReference, MemberNaming> mappedNames) {
+      super(appView);
       this.mappedNames = mappedNames;
-      this.factory = factory;
-      this.reporter = reporter;
+      this.factory = appView.dexItemFactory();
+      this.reporter = appView.options().reporter;
     }
 
     @Override
-    public DexString next(DexMethod method, MemberNamingInternalState internalState) {
-      return next(method);
+    public DexString next(DexMethod method, InternalNamingState internalState) {
+      assert !mappedNames.containsKey(method);
+      return canMinify(method, method.holder) ? super.next(method, internalState) : method.name;
     }
 
     @Override
-    public DexString next(DexField field, MemberNamingInternalState internalState) {
-      return next(field);
+    public DexString next(DexField field, InternalNamingState internalState) {
+      assert !mappedNames.containsKey(field);
+      return canMinify(field, field.holder) ? super.next(field, internalState) : field.name;
     }
 
-    private DexString next(DexReference reference) {
-      if (mappedNames.containsKey(reference)) {
-        return factory.createString(mappedNames.get(reference).getRenamedName());
-      } else if (reference.isDexMethod()) {
-        return reference.asDexMethod().name;
-      } else {
-        assert reference.isDexField();
-        return reference.asDexField().name;
+    private boolean canMinify(DexReference reference, DexType type) {
+      if (!appView.options().isMinifying()) {
+        return false;
       }
+      DexClass dexClass = appView.definitionFor(type);
+      if (dexClass == null || dexClass.isNotProgramClass()) {
+        return false;
+      }
+      return appView.rootSet().mayBeMinified(reference, appView);
     }
 
     @Override
-    public boolean breakOnNotAvailable(DexReference source, DexString name) {
-      // If we renamed a member to a name that exists in a subtype we should warn that potentially
-      // a member lookup may no longer visit its parent.
-      MemberNaming memberNaming = mappedNames.get(source);
-      assert source.isDexMethod() || source.isDexField();
-      ApplyMappingError applyMappingError = ApplyMappingError.mapToExistingMember(
-          source.toSourceString(),
-          name.toString(),
-          memberNaming == null ? Position.UNKNOWN : memberNaming.position);
-      if (source.isDexMethod()) {
-        reporter.error(applyMappingError);
-      } else {
-        // TODO(b/128868424) Check if we can remove this warning for fields.
-        reporter.warning(applyMappingError);
+    public DexString getReservedNameOrDefault(
+        DexEncodedMethod method, DexClass holder, DexString nullValue) {
+      if (mappedNames.containsKey(method.method)) {
+        return factory.createString(mappedNames.get(method.method).getRenamedName());
       }
+      return nullValue;
+    }
+
+    @Override
+    public DexString getReservedNameOrDefault(
+        DexEncodedField field, DexClass holder, DexString nullValue) {
+      if (mappedNames.containsKey(field.field)) {
+        return factory.createString(mappedNames.get(field.field).getRenamedName());
+      }
+      return nullValue;
+    }
+
+    @Override
+    public boolean allowMemberRenaming(DexClass holder) {
       return true;
     }
 
     @Override
-    public boolean noObfuscation(DexReference reference) {
-      // We have an explicit mapping from the proguard map thus everything might have to be renamed.
-      return false;
+    public void reportReservationError(DexReference source, DexString name) {
+      MemberNaming memberNaming = mappedNames.get(source);
+      assert source.isDexMethod() || source.isDexField();
+      ApplyMappingError applyMappingError =
+          ApplyMappingError.mapToExistingMember(
+              source.toSourceString(),
+              name.toString(),
+              memberNaming == null ? Position.UNKNOWN : memberNaming.position);
+      reporter.error(applyMappingError);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 81b745c..ef9aedd 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -196,114 +196,76 @@
   }
 
   private void parseMemberMappings(ClassNaming.Builder classNamingBuilder) throws IOException {
+    MemberNaming lastAddedNaming = null;
     MemberNaming activeMemberNaming = null;
-    Range previousObfuscatedRange = null;
-    boolean previousWasPotentiallySynthesized = false;
-    Signature previousSignature = null;
-    String previousRenamedName = null;
-    boolean lastRound = false;
-    for (; ; ) {
-      Signature signature = null;
+    Range previousMappedRange = null;
+    do {
       Object originalRange = null;
-      String renamedName = null;
-      Range obfuscatedRange = null;
+      Range mappedRange = null;
 
-      // In the last round we're only here to flush the last line read (which may trigger adding a
-      // new MemberNaming) and flush activeMemberNaming, so skip parsing.
-      if (!lastRound) {
-        if (!Character.isWhitespace(peekCodePoint())) {
-          lastRound = true;
-          continue;
-        }
-        skipWhitespace();
-        Object maybeRangeOrInt = maybeParseRangeOrInt();
-        if (maybeRangeOrInt != null) {
-          if (!(maybeRangeOrInt instanceof Range)) {
-            throw new ParseException(
-                String.format("Invalid obfuscated line number range (%s).", maybeRangeOrInt));
-          }
-          obfuscatedRange = (Range) maybeRangeOrInt;
-          expect(':');
-        }
-        signature = parseSignature();
-        if (peekChar(0) == ':') {
-          // This is a mapping or inlining definition
-          nextChar();
-          originalRange = maybeParseRangeOrInt();
-          if (originalRange == null) {
-            throw new ParseException("No number follows the colon after the method signature.");
-          }
-        }
-        skipWhitespace();
-        skipArrow();
-        skipWhitespace();
-        renamedName = parseMethodName();
-      }
-
-      // If this line refers to a member that should be added to classNamingBuilder (as opposed to
-      // an inner inlined callee) and it's different from the activeMemberNaming, then flush (add)
-      // the current activeMemberNaming and create a new one.
-      // We're also entering this in the last round when there's no current line.
-      if (previousRenamedName != null
-          && (!Objects.equals(previousObfuscatedRange, obfuscatedRange)
-              || !Objects.equals(previousRenamedName, renamedName)
-              || (originalRange != null && originalRange instanceof Range))) {
-        // Flush activeMemberNaming if it's for a different member.
-        if (activeMemberNaming != null) {
-          if (!activeMemberNaming.getOriginalSignature().equals(previousSignature)) {
-            classNamingBuilder.addMemberEntry(activeMemberNaming);
-            activeMemberNaming = null;
-          } else {
-            if (activeMemberNaming.getRenamedName().equals(previousRenamedName)) {
-              // The method was potentially synthesized.
-              previousWasPotentiallySynthesized = previousObfuscatedRange == null;
-            } else {
-              assert previousWasPotentiallySynthesized;
-            }
-          }
-        }
-        if (activeMemberNaming == null) {
-          activeMemberNaming =
-              new MemberNaming(previousSignature, previousRenamedName, getPosition());
-        }
-      }
-
-      if (lastRound) {
-        if (activeMemberNaming != null) {
-          classNamingBuilder.addMemberEntry(activeMemberNaming);
-        }
+      // Parse the member line '  x:y:name:z:q -> renamedName'.
+      if (!Character.isWhitespace(peekCodePoint())) {
         break;
       }
-
-      // Interpret what we've just parsed.
-      if (obfuscatedRange == null) {
-        if (originalRange != null) {
-          throw new ParseException("No mapping for original range " + originalRange + ".");
+      skipWhitespace();
+      Object maybeRangeOrInt = maybeParseRangeOrInt();
+      if (maybeRangeOrInt != null) {
+        if (!(maybeRangeOrInt instanceof Range)) {
+          throw new ParseException(
+              String.format("Invalid obfuscated line number range (%s).", maybeRangeOrInt));
         }
-        // Here we have a line like 'a() -> b' or a field like 'a -> b'
-        if (activeMemberNaming != null) {
-          classNamingBuilder.addMemberEntry(activeMemberNaming);
-        }
-        activeMemberNaming = new MemberNaming(signature, renamedName, getPosition());
-      } else {
-
-        // Note that at this point originalRange may be null which either means, it's the same as
-        // the obfuscatedRange (identity mapping) or that it's unknown (source line number
-        // information was not available).
-        assert signature instanceof MethodSignature;
+        mappedRange = (Range) maybeRangeOrInt;
+        expect(':');
       }
+      Signature signature = parseSignature();
+      if (peekChar(0) == ':') {
+        // This is a mapping or inlining definition
+        nextChar();
+        originalRange = maybeParseRangeOrInt();
+        if (originalRange == null) {
+          throw new ParseException("No number follows the colon after the method signature.");
+        }
+      }
+      if (mappedRange == null && originalRange != null) {
+        throw new ParseException("No mapping for original range " + originalRange + ".");
+      }
+
+      skipWhitespace();
+      skipArrow();
+      skipWhitespace();
+      String renamedName = parseMethodName();
 
       if (signature instanceof MethodSignature) {
         classNamingBuilder.addMappedRange(
-            obfuscatedRange, (MethodSignature) signature, originalRange, renamedName);
+            mappedRange, (MethodSignature) signature, originalRange, renamedName);
       }
 
-      previousRenamedName = renamedName;
-      previousObfuscatedRange = obfuscatedRange;
-      previousSignature = signature;
+      assert mappedRange == null || signature instanceof MethodSignature;
 
-      if (!nextLine()) {
-        lastRound = true;
+      // If this line refers to a member that should be added to classNamingBuilder (as opposed to
+      // an inner inlined callee) and it's different from the the previous activeMemberNaming, then
+      // flush (add) the current activeMemberNaming.
+      if (activeMemberNaming != null) {
+        boolean changedName = !activeMemberNaming.getRenamedName().equals(renamedName);
+        boolean changedMappedRange = !Objects.equals(previousMappedRange, mappedRange);
+        boolean notAdded =
+            lastAddedNaming == null
+                || !lastAddedNaming.getOriginalSignature().equals(activeMemberNaming.signature);
+        if (changedName || previousMappedRange == null || (changedMappedRange && notAdded)) {
+          classNamingBuilder.addMemberEntry(activeMemberNaming);
+          lastAddedNaming = activeMemberNaming;
+        }
+      }
+      activeMemberNaming = new MemberNaming(signature, renamedName, getPosition());
+      previousMappedRange = mappedRange;
+    } while (nextLine());
+
+    if (activeMemberNaming != null) {
+      boolean notAdded =
+          lastAddedNaming == null
+              || !lastAddedNaming.getOriginalSignature().equals(activeMemberNaming.signature);
+      if (previousMappedRange == null || notAdded) {
+        classNamingBuilder.addMemberEntry(activeMemberNaming);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
index 9885681..3e733fb 100644
--- a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.ReservedFieldNamingState.InternalState;
@@ -25,10 +24,6 @@
     return internalState != null && internalState.isReserved(name);
   }
 
-  public void markReservedDirectly(DexField field) {
-    markReservedDirectly(field.name, field.type);
-  }
-
   public void markReservedDirectly(DexString name, DexType type) {
     getOrCreateInternalState(type).markReservedDirectly(name);
   }
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index f6eabad..7b3caa7 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -43,12 +43,15 @@
     this.reporter = appView.options().reporter;
   }
 
-  public void run() {
+  public void run(Iterable<? extends DexClass> classes) {
     final GenericSignatureCollector genericSignatureCollector = new GenericSignatureCollector();
     final GenericSignatureParser<DexType> genericSignatureParser =
         new GenericSignatureParser<>(genericSignatureCollector);
-
-    for (DexClass clazz : appView.appInfo().classes()) {
+    // classes may not be the same as appInfo().classes() if applymapping is used on classpath
+    // arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
+    // either ProgramClass or has a mapping. This is then transitively called inside the
+    // ClassNameMinifier.
+    for (DexClass clazz : classes) {
       clazz.annotations =
           rewriteGenericSignatures(
               clazz.annotations,
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 d46a976..9d0adec 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -69,6 +69,7 @@
     private boolean overloadAggressively;
     private boolean keepRuleSynthesisForRecompilation = false;
     private boolean configurationDebugging = false;
+    private boolean dontUseMixedCaseClassnames = false;
 
     private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
       this.dexItemFactory = dexItemFactory;
@@ -275,6 +276,10 @@
       this.configurationDebugging = configurationDebugging;
     }
 
+    public void setDontUseMixedCaseClassnames(boolean dontUseMixedCaseClassnames) {
+      this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
+    }
+
     /**
      * This synthesizes a set of keep rules that are necessary in order to be able to successfully
      * recompile the generated dex files with the same keep rules.
@@ -332,7 +337,8 @@
               adaptResourceFilenames.build(),
               adaptResourceFileContents.build(),
               keepDirectories.build(),
-              configurationDebugging);
+              configurationDebugging,
+              dontUseMixedCaseClassnames);
 
       reporter.failIfPendingErrors();
 
@@ -400,6 +406,7 @@
   private final ProguardPathFilter adaptResourceFileContents;
   private final ProguardPathFilter keepDirectories;
   private final boolean configurationDebugging;
+  private final boolean dontUseMixedCaseClassnames;
 
   private ProguardConfiguration(
       String parsedConfiguration,
@@ -438,7 +445,8 @@
       ProguardPathFilter adaptResourceFilenames,
       ProguardPathFilter adaptResourceFileContents,
       ProguardPathFilter keepDirectories,
-      boolean configurationDebugging) {
+      boolean configurationDebugging,
+      boolean dontUseMixedCaseClassnames) {
     this.parsedConfiguration = parsedConfiguration;
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
@@ -476,6 +484,7 @@
     this.adaptResourceFileContents = adaptResourceFileContents;
     this.keepDirectories = keepDirectories;
     this.configurationDebugging = configurationDebugging;
+    this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
   }
 
   /**
@@ -638,6 +647,10 @@
     return configurationDebugging;
   }
 
+  public boolean hasDontUseMixedCaseClassnames() {
+    return dontUseMixedCaseClassnames;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
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 45105c2..a3d58a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -61,7 +61,6 @@
 
   private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
       "forceprocessing",
-      "dontusemixedcaseclassnames",
       "dontpreverify",
       "experimentalshrinkunusedprotofields",
       "filterlibraryjarswithorginalprogramjars",
@@ -398,6 +397,8 @@
         configurationBuilder.addRule(parseIfRule(optionStart));
       } else if (acceptString("addconfigurationdebugging")) {
         configurationBuilder.setConfigurationDebugging(true);
+      } else if (acceptString("dontusemixedcaseclassnames")) {
+        configurationBuilder.setDontUseMixedCaseClassnames(true);
       } else {
         String unknownOption = acceptString();
         String devMessage = "";
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index c5b5d6b..295de13 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -17,10 +17,6 @@
   public static final String[] EMPTY_ARRAY = {};
   public static final String LINE_SEPARATOR = System.getProperty("line.separator");
 
-  private static final char[] IDENTIFIER_LETTERS
-      = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".toCharArray();
-  private static final int NUMBER_OF_LETTERS = IDENTIFIER_LETTERS.length;
-
   public enum BraceType {
     PARENS,
     SQUARE,
@@ -257,40 +253,6 @@
     return Arrays.toString(digest);
   }
 
-  public static String numberToIdentifier(int nameCount) {
-    return numberToIdentifier(nameCount, EMPTY_CHAR_ARRAY, false);
-  }
-
-  public static String numberToIdentifier(int nameCount, char[] prefix) {
-    return numberToIdentifier(nameCount, prefix, false);
-  }
-
-  public static String numberToIdentifier(int nameCount, char[] prefix, boolean addSemicolon) {
-    // TODO(herhut): Add support for using numbers.
-    int size = addSemicolon ? 1 : 0;
-    int number = nameCount;
-    while (number >= NUMBER_OF_LETTERS) {
-      number /= NUMBER_OF_LETTERS;
-      size++;
-    }
-    size++;
-    char characters[] = Arrays.copyOfRange(prefix, 0, prefix.length + size);
-    number = nameCount;
-
-    int i = prefix.length;
-    while (number >= NUMBER_OF_LETTERS) {
-      characters[i++] = IDENTIFIER_LETTERS[number % NUMBER_OF_LETTERS];
-      number /= NUMBER_OF_LETTERS;
-    }
-    characters[i++] = IDENTIFIER_LETTERS[number - 1];
-    if (addSemicolon) {
-      characters[i++] = ';';
-    }
-    assert i == characters.length;
-
-    return new String(characters);
-  }
-
   public static String times(String string, int count) {
     StringBuilder builder = new StringBuilder();
     while (--count >= 0) {
diff --git a/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
new file mode 100644
index 0000000..b66ca5d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
+
+import java.util.Arrays;
+
+public class SymbolGenerationUtils {
+
+  public enum MixedCasing {
+    USE_MIXED_CASE,
+    DONT_USE_MIXED_CASE
+  }
+
+  // These letters are used not creating fresh names to output and not for parsing dex/class files.
+  private static final char[] IDENTIFIER_CHARACTERS =
+      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+  private static final int NUMBER_OF_CHARACTERS = IDENTIFIER_CHARACTERS.length;
+  private static final int NUMBER_OF_CHARACTERS_MINUS_CAPITAL_LETTERS = NUMBER_OF_CHARACTERS - 26;
+  private static final int NON_ALLOWED_FIRST_CHARACTERS = 10;
+
+  public static String numberToIdentifier(int nameCount, MixedCasing mixedCasing) {
+    return numberToIdentifier(nameCount, mixedCasing, EMPTY_CHAR_ARRAY, false);
+  }
+
+  public static String numberToIdentifier(int nameCount, MixedCasing mixedCasing, char[] prefix) {
+    return numberToIdentifier(nameCount, mixedCasing, prefix, false);
+  }
+
+  public static String numberToIdentifier(
+      int nameCount, MixedCasing mixedCasing, char[] prefix, boolean addSemicolon) {
+    int size = 1;
+    int number = nameCount;
+    int maximumNumberOfCharacters =
+        mixedCasing == MixedCasing.USE_MIXED_CASE
+            ? NUMBER_OF_CHARACTERS
+            : NUMBER_OF_CHARACTERS_MINUS_CAPITAL_LETTERS;
+    int firstNumberOfCharacters = maximumNumberOfCharacters - NON_ALLOWED_FIRST_CHARACTERS;
+    int availableCharacters = firstNumberOfCharacters;
+    // We first do an initial computation to find the size of the resulting string to allocate an
+    // array that will fit in length.
+    while (number > availableCharacters) {
+      number = (number - 1) / availableCharacters;
+      availableCharacters = maximumNumberOfCharacters;
+      size++;
+    }
+    size += addSemicolon ? 1 : 0;
+    char characters[] = Arrays.copyOfRange(prefix, 0, prefix.length + size);
+    number = nameCount;
+
+    int i = prefix.length;
+    availableCharacters = firstNumberOfCharacters;
+    int firstLetterPadding = NON_ALLOWED_FIRST_CHARACTERS;
+    while (number > availableCharacters) {
+      characters[i++] =
+          IDENTIFIER_CHARACTERS[(number - 1) % availableCharacters + firstLetterPadding];
+      number = (number - 1) / availableCharacters;
+      availableCharacters = maximumNumberOfCharacters;
+      firstLetterPadding = 0;
+    }
+    characters[i++] = IDENTIFIER_CHARACTERS[number - 1 + firstLetterPadding];
+    if (addSemicolon) {
+      characters[i++] = ';';
+    }
+    assert i == characters.length;
+    assert !Character.isDigit(characters[prefix.length]);
+
+    return new String(characters);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java
new file mode 100644
index 0000000..4f3a868
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassObfuscationDictionaryDuplicateTest extends TestBase {
+
+  public static class A {}
+
+  public static class B {}
+
+  public static class C {
+    public static void main(String[] args) {
+      System.out.print("HELLO WORLD!");
+    }
+  }
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public ClassObfuscationDictionaryDuplicateTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws IOException, CompilationFailedException, ExecutionException {
+    Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+    FileUtils.writeTextFile(dictionary, "a");
+
+    Set<String> finalNames = new HashSet<>();
+    finalNames.add(A.class.getPackage().getName() + ".a");
+    finalNames.add(B.class.getPackage().getName() + ".b");
+
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class, C.class)
+        .noTreeShaking()
+        .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
+        .addKeepMainRule(C.class)
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .run(parameters.getRuntime(), C.class)
+        .assertSuccessWithOutput("HELLO WORLD!")
+        .inspect(
+            inspector -> {
+              ClassSubject clazzA = inspector.clazz(A.class);
+              assertThat(clazzA, isPresent());
+              assertTrue(finalNames.contains(clazzA.getFinalName()));
+              finalNames.remove(clazzA.getFinalName());
+              ClassSubject clazzB = inspector.clazz(B.class);
+              assertThat(clazzB, isPresent());
+              assertTrue(finalNames.contains(clazzB.getFinalName()));
+            });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java b/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java
new file mode 100644
index 0000000..55b3a9a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java
@@ -0,0 +1,204 @@
+// Copyright (c) 2019, 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.SymbolGenerationUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MinificationMixedCaseAndNumbersTest extends TestBase {
+
+  private static final int NUMBER_OF_MINIFIED_CLASSES = 60;
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public MinificationMixedCaseAndNumbersTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNaming() throws ExecutionException, CompilationFailedException, IOException {
+    Set<String> allowedNames = new HashSet<>();
+    allowedNames.add("com.android.tools.r8.naming.MinificationMixedCaseAndNumbersTest$Main");
+    for (int i = 1; i < NUMBER_OF_MINIFIED_CLASSES; i++) {
+      String newString =
+          SymbolGenerationUtils.numberToIdentifier(i, MixedCasing.DONT_USE_MIXED_CASE);
+      assertFalse(Character.isDigit(newString.charAt(0)));
+      allowedNames.add("com.android.tools.r8.naming." + newString);
+    }
+    testForR8(parameters.getBackend())
+        .addInnerClasses(MinificationMixedCaseAndNumbersTest.class)
+        .addKeepMainRule(Main.class)
+        .noTreeShaking()
+        .addKeepRules("-dontusemixedcaseclassnames")
+        .setMinApi(parameters.getRuntime())
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(
+            inspector -> {
+              List<FoundClassSubject> foundClassSubjects = new ArrayList<>(inspector.allClasses());
+              foundClassSubjects.forEach(
+                  foundClazz -> {
+                    assertTrue(allowedNames.contains(foundClazz.getFinalName()));
+                    allowedNames.remove(foundClazz.getFinalName());
+                  });
+            });
+    // The first identifier to use a number is 27 for MixedCasing.DONT_USE_MIXED_CASE.
+    assertTrue(allowedNames.isEmpty());
+    assertEquals(
+        "a0", SymbolGenerationUtils.numberToIdentifier(27, MixedCasing.DONT_USE_MIXED_CASE));
+    assertEquals(
+        "zz", SymbolGenerationUtils.numberToIdentifier(962, MixedCasing.DONT_USE_MIXED_CASE));
+    assertEquals(
+        "a00", SymbolGenerationUtils.numberToIdentifier(963, MixedCasing.DONT_USE_MIXED_CASE));
+  }
+
+  public static class A {}
+
+  public static class B {}
+
+  public static class C {}
+
+  public static class D {}
+
+  public static class E {}
+
+  public static class F {}
+
+  public static class G {}
+
+  public static class H {}
+
+  public static class I {}
+
+  public static class J {}
+
+  public static class K {}
+
+  public static class L {}
+
+  public static class M {}
+
+  public static class N {}
+
+  public static class O {}
+
+  public static class P {}
+
+  public static class Q {}
+
+  public static class R {}
+
+  public static class S {}
+
+  public static class T {}
+
+  public static class U {}
+
+  public static class V {}
+
+  public static class W {}
+
+  public static class X {}
+
+  public static class Y {}
+
+  public static class Z {}
+
+  public static class a {}
+
+  public static class b {}
+
+  public static class c {}
+
+  public static class d {}
+
+  public static class e {}
+
+  public static class f {}
+
+  public static class g {}
+
+  public static class h {}
+
+  public static class i {}
+
+  public static class j {}
+
+  public static class k {}
+
+  public static class l {}
+
+  public static class m {}
+
+  public static class n {}
+
+  public static class o {}
+
+  public static class p {}
+
+  public static class q {}
+
+  public static class r {}
+
+  public static class s {}
+
+  public static class t {}
+
+  public static class u {}
+
+  public static class v {}
+
+  public static class w {}
+
+  public static class x {}
+
+  public static class y {}
+
+  public static class z {}
+
+  public static class AA {}
+
+  public static class AB {}
+
+  public static class AC {}
+
+  public static class AD {}
+
+  public static class AE {}
+
+  public static class AF {}
+
+  public static class AG {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println("HELLO WORLD");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
new file mode 100644
index 0000000..f10947e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2019, 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 junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.keeppackagenames.Top;
+import com.android.tools.r8.naming.packageobfucationdict.A;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class PackageObfuscationDictionaryDuplicateTest extends TestBase {
+
+  public static class C {
+    public static void main(String[] args) {
+      System.out.print("HELLO WORLD!");
+    }
+  }
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public PackageObfuscationDictionaryDuplicateTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws IOException, CompilationFailedException, ExecutionException {
+    Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+    FileUtils.writeTextFile(dictionary, "a");
+    testForR8(parameters.getBackend())
+        .addProgramClassesAndInnerClasses(Top.class, A.class, C.class)
+        .noTreeShaking()
+        .addKeepRules("-packageobfuscationdictionary " + dictionary.toString())
+        .addKeepMainRule(C.class)
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .run(parameters.getRuntime(), C.class)
+        .assertSuccessWithOutput("HELLO WORLD!")
+        .inspect(
+            inspector -> {
+              ClassSubject clazzTop = inspector.clazz(Top.class);
+              assertTrue(clazzTop.getFinalName().endsWith(".a.a"));
+              ClassSubject clazzA = inspector.clazz(A.class);
+              assertTrue(clazzA.getFinalName().endsWith(".b.a"));
+            });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java b/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
index 91da18d..4c265ef 100644
--- a/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
+++ b/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
@@ -162,4 +162,34 @@
       assertEquals(2, ((TextPosition) diagnostic.getPosition()).getLine());
     }
   }
+
+  @Test
+  public void testInliningFrames() throws IOException {
+    Path applyMappingFile =
+        getApplyMappingFile(
+            "A.B.C -> a:",
+            "  int foo(A) -> a",
+            "  1:2:int bar(A):3:4 -> a",
+            "  1:2:int baz(B):3 -> a");
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+    Reporter reporter = new Reporter(testDiagnosticMessages);
+    SeedMapper.seedMapperFromFile(reporter, applyMappingFile);
+  }
+
+  @Test
+  public void testDuplicateInliningFrames() throws IOException {
+    Path applyMappingFile =
+        getApplyMappingFile(
+            "A.B.C -> a:",
+            "  int foo(Z) -> a",
+            "  1:1:int qux(A):3:3 -> a",
+            "  1:1:int bar(A):3 -> a",
+            "  2:2:int qux(A):3:3 -> a",
+            "  2:2:int bar(A):4 -> a",
+            "  3:3:int bar(A):5:5 -> a",
+            "  int qux(C) -> a");
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+    Reporter reporter = new Reporter(testDiagnosticMessages);
+    SeedMapper.seedMapperFromFile(reporter, applyMappingFile);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
index 500dbf5..2587730 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Assume;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -99,7 +98,6 @@
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
-  @Ignore("b/126503704")
   @Test
   public void devirtualizingNoRenamingOfOverriddenNotKeptInterfaceMethods() throws Exception {
     R8TestCompileResult libraryResult =
@@ -131,7 +129,6 @@
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
-  @Ignore("b/126503704")
   @Test
   public void devirtualizingNoRenamingOfOverriddenKeptInterfaceMethods() throws Exception {
     R8TestCompileResult libraryResult =
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
new file mode 100644
index 0000000..b920732
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2019, 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.applymapping;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApplyMappingMinificationTest extends TestBase {
+
+  @NeverClassInline
+  public static class A {
+    public int fieldA = 1;
+    public int fieldB = 2;
+
+    @NeverInline
+    public void methodA() {
+      System.out.println("A.methodA");
+    }
+
+    @NeverInline
+    public void methodB() {
+      System.out.println("A.methodB");
+    }
+
+    @NeverInline
+    public void methodC() {
+      System.out.println("A.methodC");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    @NeverInline
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  public static class C {
+
+    public static void main(String[] args) {
+      System.out.println(new A().fieldA);
+      System.out.println(new A().fieldB);
+      new A().methodA();
+      new A().methodB();
+      new A().methodC();
+      new B().foo();
+    }
+  }
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public ApplyMappingMinificationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testApplyMappingFollowedByMinification()
+      throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+    String[] pgMap =
+        new String[] {
+          A.class.getTypeName() + " -> a:", "  int fieldA -> a", "  void methodA() -> a"
+        };
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(ApplyMappingMinificationTest.class)
+            .addKeepMainRule(C.class)
+            .addKeepMethodRules(methodFromMethod(A.class.getDeclaredMethod("methodC")))
+            .enableInliningAnnotations()
+            .enableClassInliningAnnotations()
+            .addApplyMapping(StringUtils.lines(pgMap))
+            .setMinApi(parameters.getRuntime())
+            .run(parameters.getRuntime(), C.class)
+            .assertSuccessWithOutputLines("1", "2", "A.methodA", "A.methodB", "A.methodC", "B.foo")
+            .inspect(
+                inspector -> {
+                  ClassSubject clazzB = inspector.clazz(B.class);
+                  assertThat(clazzB, isPresent());
+                  assertTrue(clazzB.isRenamed());
+                  ClassSubject clazzA = inspector.clazz(A.class);
+                  assertThat(clazzA, isPresent());
+                  assertEquals("a", clazzA.getFinalName());
+                  FieldSubject fieldA = clazzA.uniqueFieldWithName("fieldA");
+                  assertThat(fieldA, isPresent());
+                  assertEquals("a", fieldA.getFinalName());
+                  MethodSubject methodA = clazzA.uniqueMethodWithName("methodA");
+                  assertThat(methodA, isPresent());
+                  assertEquals("a", methodA.getFinalName());
+                  FieldSubject fieldB = clazzA.uniqueFieldWithName("fieldB");
+                  assertThat(fieldB, isPresent());
+                  assertTrue(fieldB.isRenamed());
+                  MethodSubject methodB = clazzA.uniqueMethodWithName("methodB");
+                  assertThat(methodB, isPresent());
+                  assertTrue(methodB.isRenamed());
+                  MethodSubject methodC = clazzA.uniqueMethodWithName("methodC");
+                  assertThat(methodC, isPresent());
+                  assertFalse(methodC.isRenamed());
+                });
+    // Ensure that the proguard map is extended with all the new minified names.
+    for (String pgLine : pgMap) {
+      runResult.proguardMap().contains(pgLine);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
new file mode 100644
index 0000000..ff87272
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2019, 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.applymapping;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApplyMappingRotateNameClashTest extends TestBase {
+
+  public static class A {}
+
+  public static class B {}
+
+  public static class C {
+    public static void main(String[] args) {
+      System.out.print("HELLO WORLD!");
+    }
+  }
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public ApplyMappingRotateNameClashTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test_b131532229() throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addLibraryClasses(A.class, B.class)
+        .addLibraryFiles(TestBase.runtimeJar(parameters.getBackend()))
+        .addProgramClasses(C.class)
+        .addKeepMainRule(C.class)
+        .noTreeShaking()
+        .addApplyMapping(
+            StringUtils.lines(
+                A.class.getTypeName() + " -> " + B.class.getTypeName() + ":",
+                B.class.getTypeName() + " -> " + A.class.getTypeName() + ":"))
+        .setMinApi(parameters.getRuntime())
+        .run(parameters.getRuntime(), C.class)
+        .assertSuccessWithOutput("HELLO WORLD!");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
index 7989a29..cc4e99c 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.LibraryBase;
+import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.LibraryInterface;
 import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.LibrarySubclass;
 import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.ProgramClass;
 import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.ProgramSubclass;
@@ -20,7 +21,6 @@
 import java.nio.file.Path;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -28,17 +28,28 @@
 @RunWith(Parameterized.class)
 public class ApplyMappingVirtualInvokeTest extends TestBase {
 
-  public static final String EXPECTED_PROGRAM = StringUtils.lines("LibraryBase.foo");
+  public static final String EXPECTED_PROGRAM =
+      StringUtils.lines("LibraryBase.foo", "LibraryBase.bar");
   public static final String EXPECTED_PROGRAM_SUBCLASS =
-      StringUtils.lines("ProgramSubclass.foo", "LibraryBase.foo");
+      StringUtils.lines(
+          "ProgramSubclass.foo", "LibraryBase.foo", "ProgramSubclass.bar", "LibraryBase.bar");
 
   public static class TestClasses {
 
-    public static class LibraryBase {
+    public interface LibraryInterface {
+      void bar();
+    }
 
-      void foo() {
+    public static class LibraryBase implements LibraryInterface {
+
+      public void foo() {
         System.out.println("LibraryBase.foo");
       }
+
+      @Override
+      public void bar() {
+        System.out.println("LibraryBase.bar");
+      }
     }
 
     public static class LibrarySubclass extends LibraryBase {}
@@ -47,6 +58,7 @@
 
       public static void main(String[] args) {
         new LibrarySubclass().foo();
+        new LibrarySubclass().bar();
       }
     }
 
@@ -54,17 +66,26 @@
 
       public static void main(String[] args) {
         new ProgramSubclass().foo();
+        new ProgramSubclass().bar();
       }
 
       @Override
-      void foo() {
+      public void foo() {
         System.out.println("ProgramSubclass.foo");
         super.foo();
       }
+
+      @Override
+      public void bar() {
+        System.out.println("ProgramSubclass.bar");
+        super.bar();
+      }
     }
   }
 
-  private static final Class<?>[] LIBRARY_CLASSES = {LibraryBase.class, LibrarySubclass.class};
+  private static final Class<?>[] LIBRARY_CLASSES = {
+    LibraryInterface.class, LibraryBase.class, LibrarySubclass.class
+  };
   private static final Class<?>[] PROGRAM_CLASSES = {ProgramClass.class, ProgramSubclass.class};
   private final TestParameters parameters;
 
@@ -77,13 +98,15 @@
     this.parameters = parameters;
   }
 
-  private static Function<Backend, R8TestCompileResult> compilationResults =
+  private static Function<TestParameters, R8TestCompileResult> compilationResults =
       memoizeFunction(ApplyMappingVirtualInvokeTest::compile);
 
-  private static R8TestCompileResult compile(Backend backend) throws CompilationFailedException {
-    return testForR8(getStaticTemp(), backend)
+  private static R8TestCompileResult compile(TestParameters parameters)
+      throws CompilationFailedException {
+    return testForR8(getStaticTemp(), parameters.getBackend())
         .addProgramClasses(LIBRARY_CLASSES)
         .addKeepClassAndMembersRulesWithAllowObfuscation(LIBRARY_CLASSES)
+        .setMinApi(parameters.getRuntime())
         .addOptionsModification(
             options -> {
               options.enableInlining = false;
@@ -116,14 +139,12 @@
         .assertSuccessWithOutput(EXPECTED_PROGRAM_SUBCLASS);
   }
 
-  @Ignore("b/128868424")
   @Test
   public void testProgramClass()
       throws ExecutionException, CompilationFailedException, IOException {
     runTest(ProgramClass.class, EXPECTED_PROGRAM);
   }
 
-  @Ignore("b/128868424")
   @Test
   public void testProgramSubClass()
       throws ExecutionException, IOException, CompilationFailedException {
@@ -132,7 +153,7 @@
 
   private void runTest(Class<?> main, String expected)
       throws ExecutionException, IOException, CompilationFailedException {
-    R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters.getBackend());
+    R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters);
     Path outPath = temp.newFile("out.zip").toPath();
     libraryCompileResult.writeToZip(outPath);
     testForR8(parameters.getBackend())
@@ -140,7 +161,7 @@
         .noMinification()
         .addProgramClasses(PROGRAM_CLASSES)
         .addApplyMapping(libraryCompileResult.getProguardMap())
-        .addLibraryClasses(LIBRARY_CLASSES)
+        .addClasspathClasses(LIBRARY_CLASSES)
         .addLibraryFiles(runtimeJar(parameters.getBackend()))
         .setMinApi(parameters.getRuntime())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
index 3bcc25c..58cdef6 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
@@ -99,6 +99,7 @@
         .addProgramClasses(ProgramClass.class)
         .addClasspathClasses(LibraryInterface.class)
         .addApplyMapping(libraryResult.getProguardMap())
+        .addKeepMainRule(ProgramClass.class)
         .setMinApi(parameters.getRuntime())
         .compile()
         .addRunClasspathFiles(libraryResult.writeToZip())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
index 0fdbe2d..c2f0b8f 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
@@ -20,7 +20,6 @@
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
@@ -140,6 +139,7 @@
         .addKeepMainRule(MAIN)
         .addKeepRules("-applymapping " + mappingFile)
         .noTreeShaking()
+        .noMinification()
         .compile()
         .run(MAIN)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
@@ -181,18 +181,6 @@
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
-  private void testR8_minifiedLibraryJar(Path mappingFile) throws Exception {
-    testForR8(Backend.DEX)
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), libJar)
-        .addProgramFiles(prgJarThatUsesMinifiedLib)
-        .addKeepMainRule(MAIN)
-        .addKeepRules("-applymapping " + mappingFile)
-        .noTreeShaking()
-        .compile()
-        .run(MAIN)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
-  }
-
   @Test
   public void testProguard_prgClassRenamedToExistingPrgClass() throws Exception {
     FileUtils.writeTextFile(mappingFile, mappingToAlreadyMappedName());
@@ -432,11 +420,4 @@
       assertThat(e.getMessage(), containsString("can't find superclass or interface A"));
     }
   }
-
-  @Ignore("b/121305642")
-  @Test
-  public void testR8_minifiedLib() throws Exception {
-    FileUtils.writeTextFile(mappingFile, invertedMapping());
-    testR8_minifiedLibraryJar(mappingFile);
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
index f64424e..ec50858 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
@@ -29,7 +29,6 @@
 import java.util.Iterator;
 import java.util.concurrent.ExecutionException;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -70,7 +69,6 @@
     out = temp.newFolder("out").toPath();
   }
 
-  @Ignore("b/128516926")
   @Test
   public void test044_apply() throws Exception {
     Path flag =
diff --git a/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java b/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java
new file mode 100644
index 0000000..012140e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2019, 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.packageobfucationdict;
+
+public class A {}