Merge "Revert "Forward GraphLense to IRBuilder""
diff --git a/build.gradle b/build.gradle
index 33d967f..5606a9e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -567,6 +567,7 @@
     from repackageSources.outputs.files
     from repackageDeps.outputs.files
     configureRelocations(it)
+    exclude "META-INF/*.kotlin_module"
 }
 
 task r8WithoutDeps(type: ShadowJar) {
@@ -596,6 +597,7 @@
     } else {
         from sourceSets.main.output
     }
+    exclude "META-INF/*.kotlin_module"
 }
 
 task R8NoManifestNoDeps(type: ShadowJar) {
@@ -619,6 +621,7 @@
     } else {
         from sourceSets.main.output
     }
+    exclude "META-INF/*.kotlin_module"
 }
 
 task D8(type: ShadowJar) {
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 7b17ec8..c5f0a40 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.concurrent.ExecutionException;
@@ -88,13 +90,14 @@
         "Usage: disasm [options] <input-files>\n"
             + " where <input-files> are dex files\n"
             + " and options are:\n"
-            + "  --all                   # Include all information in disassembly.\n"
-            + "  --smali                 # Disassemble using smali syntax.\n"
-            + "  --ir                    # Print IR before and after optimization.\n"
-            + "  --pg-map <file>         # Proguard map <file> for mapping names.\n"
-            + "  --output                # Specify a file or directory to write to.\n"
-            + "  --version               # Print the version of r8.\n"
-            + "  --help                  # Print this message.";
+            + "  --all                       # Include all information in disassembly.\n"
+            + "  --smali                     # Disassemble using smali syntax.\n"
+            + "  --ir                        # Print IR before and after optimization.\n"
+            + "  --pg-map <file>             # Proguard map <file> for mapping names.\n"
+            + "  --pg-map-charset <charset>  # Charset for Proguard map file.\n"
+            + "  --output                    # Specify a file or directory to write to.\n"
+            + "  --version                   # Print the version of r8.\n"
+            + "  --help                      # Print this message.";
 
     private final boolean allInfo;
     private final boolean useSmali;
@@ -127,6 +130,18 @@
           builder.setUseIr(true);
         } else if (arg.equals("--pg-map")) {
           builder.setProguardMapFile(Paths.get(args[++i]));
+        } else if (arg.equals("--pg-map-charset")) {
+          String charset = args[++i];
+          try {
+            Charset.forName(charset);
+          } catch (UnsupportedCharsetException e) {
+            builder.getReporter().error(
+                new StringDiagnostic(
+                    "Unsupported charset: " + charset + "." + System.lineSeparator()
+                        + "Supported charsets are: "
+                        + String.join(", ", Charset.availableCharsets().keySet()),
+                CommandLineOrigin.INSTANCE));
+          }
         } else if (arg.equals("--output")) {
           String outputPath = args[++i];
           builder.setOutputPath(Paths.get(outputPath));
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index fd85c25..c9f2032 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -70,6 +70,12 @@
     return extractMarker(app);
   }
 
+  public static Collection<Marker> extractMarkerFromClassProgramData(byte[] data)
+      throws IOException, ExecutionException {
+    AndroidApp app = AndroidApp.builder().addClassProgramData(data, Origin.unknown()).build();
+    return extractMarker(app);
+  }
+
   private static void addDexResources(AndroidApp.Builder appBuilder, Path file)
       throws IOException, ResourceException {
     if (FileUtils.isVDexFile(file)) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 30b57da..c59b56b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -194,6 +194,7 @@
         new CfApplicationWriter(
                 application,
                 options,
+                marker,
                 deadCode,
                 graphLense,
                 namingLens,
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 3f06bbc..11d9730 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -74,6 +74,7 @@
 
         private int argumentIndex = -1;
         private boolean isAlwaysNull = false;
+        private DexType type = null;
 
         public Builder setArgumentIndex(int argumentIndex) {
           this.argumentIndex = argumentIndex;
@@ -85,18 +86,26 @@
           return this;
         }
 
+        public Builder setType(DexType type) {
+          this.type = type;
+          return this;
+        }
+
         public RemovedArgumentInfo build() {
           assert argumentIndex >= 0;
-          return new RemovedArgumentInfo(argumentIndex, isAlwaysNull);
+          assert type != null;
+          return new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type);
         }
       }
 
       private final int argumentIndex;
       private final boolean isAlwaysNull;
+      private final DexType type;
 
-      private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull) {
+      private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull, DexType type) {
         this.argumentIndex = argumentIndex;
         this.isAlwaysNull = isAlwaysNull;
+        this.type = type;
       }
 
       public static Builder builder() {
@@ -107,6 +116,10 @@
         return argumentIndex;
       }
 
+      public DexType getType() {
+        return type;
+      }
+
       public boolean isAlwaysNull() {
         return isAlwaysNull;
       }
@@ -117,7 +130,7 @@
 
       public RemovedArgumentInfo withArgumentIndex(int argumentIndex) {
         return this.argumentIndex != argumentIndex
-            ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull)
+            ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type)
             : this;
       }
     }
@@ -694,8 +707,8 @@
    * <p>Subclasses can override the lookup methods.
    *
    * <p>For method mapping where invocation type can change just override {@link
-   * #mapInvocationType(DexMethod, DexMethod, DexMethod, Type)} if the default name mapping applies,
-   * and only invocation type might need to change.
+   * #mapInvocationType(DexMethod, DexMethod, Type)} if the default name mapping applies, and only
+   * invocation type might need to change.
    */
   public static class NestedGraphLense extends GraphLense {
 
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 55ece50..3170b63 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
@@ -110,6 +111,22 @@
     reader.accept(
         new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
         parsingOptions);
+
+    // Read marker.
+    if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
+        && reader.getItem(CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
+      try {
+        Object maybeMarker =
+            reader.readConst(
+                CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
+                new char[reader.getMaxStringLength()]);
+        if (maybeMarker instanceof String) {
+          application.getFactory().createString((String) maybeMarker);
+        }
+      } catch (IllegalArgumentException e) {
+        // Ignore if the type of the constant is not something readConst() allows.
+      }
+    }
   }
 
   private static int cleanAccessFlags(int access) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 881b255..4738403 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -38,9 +38,9 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentInfo;
+import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CanonicalPositions;
@@ -50,14 +50,14 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 
 public class DexSourceCode implements SourceCode {
 
   private final DexCode code;
-  private final MethodAccessFlags accessFlags;
-  private final DexProto proto;
+  private final DexEncodedMethod method;
 
   // Mapping from instruction offset to instruction index in the DexCode instruction array.
   private final Map<Integer, Integer> offsetToInstructionIndex = new HashMap<>();
@@ -73,8 +73,6 @@
   private Position currentPosition = null;
   private final CanonicalPositions canonicalPositions;
 
-  private final List<TypeLatticeElement> argumentTypes;
-
   private List<DexDebugEntry> debugEntries = null;
   // In case of inlining the position of the invoke in the caller.
   private final DexMethod originalMethod;
@@ -86,11 +84,8 @@
       Position callerPosition,
       AppInfo appInfo) {
     this.code = code;
-    this.proto = method.method.proto;
-    this.accessFlags = method.accessFlags;
+    this.method = method;
     this.originalMethod = originalMethod;
-
-    argumentTypes = computeArgumentTypes(appInfo);
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
       debugEntries = info.computeEntries(originalMethod);
@@ -149,16 +144,46 @@
     if (code.incomingRegisterSize == 0) {
       return;
     }
+
+    RemovedArgumentsInfo removedArgumentsInfo = builder.prototypeChanges.getRemovedArgumentsInfo();
+    ListIterator<RemovedArgumentInfo> removedArgumentIterator = removedArgumentsInfo.iterator();
+    RemovedArgumentInfo nextRemovedArgument =
+        removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+
     // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
     // block.
+    int argumentIndex = 0;
+
     int register = code.registerSize - code.incomingRegisterSize;
-    if (!accessFlags.isStatic()) {
+    if (!method.isStatic()) {
       builder.addThisArgument(register);
+      ++argumentIndex;
       ++register;
     }
-    for (TypeLatticeElement typeLattice : argumentTypes) {
-      builder.addNonThisArgument(register, typeLattice);
-      register += typeLattice.requiredRegisters();
+
+    int numberOfArguments =
+        method.method.proto.parameters.values.length
+            + removedArgumentsInfo.numberOfRemovedArguments()
+            + (method.isStatic() ? 0 : 1);
+
+    for (int usedArgumentIndex = 0; argumentIndex < numberOfArguments; ++argumentIndex) {
+      TypeLatticeElement type;
+      if (nextRemovedArgument != null && nextRemovedArgument.getArgumentIndex() == argumentIndex) {
+        type =
+            TypeLatticeElement.fromDexType(
+                nextRemovedArgument.getType(), Nullability.maybeNull(), builder.getAppInfo());
+        builder.addConstantOrUnusedArgument(register);
+        nextRemovedArgument =
+            removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+      } else {
+        type =
+            TypeLatticeElement.fromDexType(
+                method.method.proto.parameters.values[usedArgumentIndex++],
+                Nullability.maybeNull(),
+                builder.getAppInfo());
+        builder.addNonThisArgument(register, type);
+      }
+      register += type.requiredRegisters();
     }
     builder.flushArgumentInstructions();
   }
@@ -310,14 +335,6 @@
         arrayFilledDataPayloadResolver.getData(payloadOffset));
   }
 
-  private List<TypeLatticeElement> computeArgumentTypes(AppInfo appInfo) {
-    List<TypeLatticeElement> types = new ArrayList<>(proto.parameters.size());
-    for (DexType type : proto.parameters.values) {
-      types.add(TypeLatticeElement.fromDexType(type, Nullability.maybeNull(), appInfo));
-    }
-    return types;
-  }
-
   private boolean isInvoke(Instruction dex) {
     return dex instanceof InvokeDirect
         || dex instanceof InvokeDirectRange
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 045ba3e..37b3d1b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -396,7 +396,7 @@
   private DexEncodedMethod context;
   private final AppInfo appInfo;
   private final Origin origin;
-  private RewrittenPrototypeDescription prototypeChanges;
+  final RewrittenPrototypeDescription prototypeChanges;
   private ListIterator<RemovedArgumentInfo> removedArgumentsIterator;
   private int argumentCount = 0;
 
@@ -894,15 +894,8 @@
       DebugLocalInfo local = getOutgoingLocal(register);
       Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
       addInstruction(new Argument(value));
-    } else if (removedArgumentInfo.isAlwaysNull()) {
-      if (pendingArgumentInstructions == null) {
-        pendingArgumentInstructions = new ArrayList<>();
-      }
-      DebugLocalInfo local = getOutgoingLocal(register);
-      Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
-      pendingArgumentInstructions.add(new ConstNumber(value, 0));
     } else {
-      assert removedArgumentInfo.isNeverUsed();
+      handleConstantOrUnusedArgument(register, removedArgumentInfo);
     }
   }
 
@@ -918,6 +911,25 @@
     }
   }
 
+  public void addConstantOrUnusedArgument(int register) {
+    handleConstantOrUnusedArgument(register, getRemovedArgumentInfo());
+  }
+
+  private void handleConstantOrUnusedArgument(
+      int register, RemovedArgumentInfo removedArgumentInfo) {
+    assert removedArgumentInfo != null;
+    if (removedArgumentInfo.isAlwaysNull()) {
+      if (pendingArgumentInstructions == null) {
+        pendingArgumentInstructions = new ArrayList<>();
+      }
+      DebugLocalInfo local = getOutgoingLocal(register);
+      Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
+      pendingArgumentInstructions.add(new ConstNumber(value, 0));
+    } else {
+      assert removedArgumentInfo.isNeverUsed();
+    }
+  }
+
   public void flushArgumentInstructions() {
     if (pendingArgumentInstructions != null) {
       pendingArgumentInstructions.forEach(this::addInstruction);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c1364f6..e41593f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -298,7 +298,11 @@
           removedArgumentsInfo = new ArrayList<>();
         }
         removedArgumentsInfo.add(
-            RemovedArgumentInfo.builder().setArgumentIndex(i + offset).setIsAlwaysNull().build());
+            RemovedArgumentInfo.builder()
+                .setArgumentIndex(i + offset)
+                .setIsAlwaysNull()
+                .setType(type)
+                .build());
       }
     }
     return removedArgumentsInfo != null
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 061907f..aae8429 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
@@ -208,8 +208,8 @@
       return null;
     }
     assert method.getCode().getOwner() == method;
-    int argumentCount =
-        method.method.proto.parameters.size() + (method.accessFlags.isStatic() ? 0 : 1);
+    int offset = method.accessFlags.isStatic() ? 0 : 1;
+    int argumentCount = method.method.proto.parameters.size() + offset;
     // TODO(65810338): Implement for virtual methods as well.
     if (method.accessFlags.isPrivate()
         || method.accessFlags.isStatic()
@@ -224,9 +224,13 @@
       BitSet used = collector.getUsedArguments();
       if (used.cardinality() < argumentCount) {
         List<RemovedArgumentInfo> unused = new ArrayList<>();
-        for (int i = 0; i < argumentCount; i++) {
-          if (!used.get(i)) {
-            unused.add(RemovedArgumentInfo.builder().setArgumentIndex(i).build());
+        for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) {
+          if (!used.get(argumentIndex)) {
+            unused.add(
+                RemovedArgumentInfo.builder()
+                    .setArgumentIndex(argumentIndex)
+                    .setType(method.method.proto.parameters.values[argumentIndex - offset])
+                    .build());
           }
         }
         return new RemovedArgumentsInfo(unused);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 14459fe..05120db 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.Code;
@@ -61,10 +62,15 @@
   private static final boolean RUN_VERIFIER = false;
   private static final boolean PRINT_CF = false;
 
+  // First item inserted into the constant pool is the marker string which generates an UTF8 to
+  // pool index #1 and a String entry to #2, referencing #1.
+  public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
+
   private final DexApplication application;
   private final GraphLense graphLense;
   private final NamingLens namingLens;
   private final InternalOptions options;
+  private final String markerString;
 
   public final ProguardMapSupplier proguardMapSupplier;
   public final String deadCode;
@@ -73,6 +79,7 @@
   public CfApplicationWriter(
       DexApplication application,
       InternalOptions options,
+      Marker marker,
       String deadCode,
       GraphLense graphLense,
       NamingLens namingLens,
@@ -82,6 +89,7 @@
     this.graphLense = graphLense;
     this.namingLens = namingLens;
     this.options = options;
+    this.markerString = marker == null ? null : marker.toString();
     this.proguardMapSupplier = proguardMapSupplier;
     this.deadCode = deadCode;
     this.proguardSeedsData = proguardSeedsData;
@@ -117,6 +125,10 @@
 
   private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
     ClassWriter writer = new ClassWriter(0);
+    if (markerString != null) {
+      int index = writer.newConst(markerString);
+      assert index == MARKER_STRING_CONSTANT_POOL_INDEX;
+    }
     writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
     int version = getClassFileVersion(clazz);
     int access = clazz.accessFlags.getAsCfAccessFlags();
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index cdf96c2..706620f 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -171,6 +171,11 @@
         return null;
       }
       DexType holderType = classValue.getConstInstruction().asConstClass().getValue();
+      if (holderType.isArrayType()) {
+        // None of the fields or methods of an array type will be renamed, since they are all
+        // declared in the library. Hence there is no need to handle this case.
+        return null;
+      }
       DexClass holder = appInfo.definitionFor(holderType);
       if (holder == null) {
         return null;
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 9934ab2..5547315 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -318,7 +318,8 @@
       throw new ParseException("Identifier expected");
     }
     nextCodePoint();
-    while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())) {
+    while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())
+        || IdentifierUtils.isQuestionMark(peekCodePoint())) {
       nextCodePoint();
     }
     if (isInit) {
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 5a12e62..6a47607 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -15,8 +16,9 @@
 public class ExtractMarkerTest {
 
   @Test
-  public void extractMarkerTest() throws CompilationFailedException {
+  public void extractMarkerTestDex() throws CompilationFailedException {
     String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+    boolean[] testExecuted = {false};
     D8.run(
         D8Command.builder()
             .addProgramFiles(Paths.get(classFile))
@@ -42,8 +44,46 @@
                     assertEquals(
                         CompilationMode.DEBUG.toString().toLowerCase(),
                         marker.getCompilationMode());
+                    testExecuted[0] = true;
                   }
                 })
             .build());
+    assertTrue(testExecuted[0]);
+  }
+
+  @Test
+  public void extractMarkerTestCf() throws CompilationFailedException {
+    String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+    boolean[] testExecuted = {false};
+    R8.run(
+        R8Command.builder()
+            .addProgramFiles(Paths.get(classFile))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setProgramConsumer(
+                new ClassFileConsumer.ForwardingConsumer(null) {
+                  @Override
+                  public void accept(
+                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                    Marker marker;
+                    try {
+                      Collection<Marker> markers =
+                          ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+                      assertEquals(1, markers.size());
+                      marker = markers.iterator().next();
+                    } catch (Exception e) {
+                      throw new RuntimeException(e);
+                    }
+                    assertEquals(Tool.R8, marker.getTool());
+                    assertEquals(Version.LABEL, marker.getVersion());
+                    assertEquals(
+                        CompilationMode.DEBUG.toString().toLowerCase(),
+                        marker.getCompilationMode());
+                    testExecuted[0] = true;
+                  }
+                })
+            .build());
+    assertTrue(testExecuted[0]);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
index fc382ee..6b281cb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.Streams;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -41,6 +42,7 @@
   }
 
   @Test
+  @Ignore("b/123284765")
   public void test() throws Exception {
     String expectedOutput =
         StringUtils.lines(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
new file mode 100644
index 0000000..71c5fbe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
@@ -0,0 +1,121 @@
+// 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.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InliningFromCurrentClassTest extends TestBase {
+
+  @Ignore("b/123327416")
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines(
+            "In A.<clinit>()",
+            "In B.<clinit>()",
+            "In A.inlineable1()",
+            "In B.inlineable2()",
+            "In C.<clinit>()",
+            "In C.notInlineable()");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(InliningFromCurrentClassTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    ClassSubject classB = inspector.clazz(B.class);
+    assertThat(classB, isPresent());
+
+    ClassSubject classC = inspector.clazz(C.class);
+    assertThat(classC, isPresent());
+
+    MethodSubject inlineable1Method = classA.uniqueMethodWithName("inlineable1");
+    assertThat(inlineable1Method, not(isPresent()));
+
+    MethodSubject inlineable2Method = classB.uniqueMethodWithName("inlineable2");
+    assertThat(inlineable2Method, not(isPresent()));
+
+    MethodSubject notInlineableMethod = classC.uniqueMethodWithName("notInlineable");
+    assertThat(notInlineableMethod, isPresent());
+
+    MethodSubject testMethod = classB.uniqueMethodWithName("test");
+    assertThat(testMethod, isPresent());
+    assertThat(testMethod, not(invokesMethod(inlineable1Method)));
+    assertThat(testMethod, not(invokesMethod(inlineable2Method)));
+    assertThat(testMethod, invokesMethod(notInlineableMethod));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      B.test();
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    static {
+      System.out.println("In A.<clinit>()");
+    }
+
+    static void inlineable1() {
+      System.out.println("In A.inlineable1()");
+    }
+  }
+
+  @NeverMerge
+  static class B extends A {
+
+    static {
+      System.out.println("In B.<clinit>()");
+    }
+
+    @NeverInline
+    static void test() {
+      A.inlineable1();
+      B.inlineable2();
+      C.notInlineable();
+    }
+
+    static void inlineable2() {
+      System.out.println("In B.inlineable2()");
+    }
+  }
+
+  static class C extends B {
+
+    static {
+      System.out.println("In C.<clinit>()");
+    }
+
+    static void notInlineable() {
+      System.out.println("In C.notInlineable()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
new file mode 100644
index 0000000..9691149
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
@@ -0,0 +1,87 @@
+// 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.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InliningWithClassInitializerTest extends TestBase {
+
+  @Ignore("b/123327413")
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines("In A.<clinit>()", "In B.inlineable()", "In B.other()");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(InliningWithClassInitializerTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    ClassSubject classB = inspector.clazz(B.class);
+    assertThat(classB, isPresent());
+
+    MethodSubject inlineableMethod = classB.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+
+    MethodSubject otherMethod = classB.uniqueMethodWithName("other");
+    assertThat(otherMethod, isPresent());
+
+    MethodSubject mainMethod = inspector.clazz(TestClass.class).mainMethod();
+    assertThat(mainMethod, invokesMethod(otherMethod));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // Should be inlined since the call to `B.other()` will ensure that the static initalizer in
+      // A will continue to be executed even after inlining.
+      B.inlineable();
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    static {
+      System.out.println("In A.<clinit>()");
+    }
+  }
+
+  static class B extends A {
+
+    static void inlineable() {
+      System.out.println("In B.inlineable()");
+      other();
+    }
+
+    @NeverInline
+    static void other() {
+      System.out.println("In B.other()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
index 8a8f137..7cb49d3 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -29,7 +29,13 @@
     String mapping =
         "com.c.c.b -> com.c.c.b:\n" +
             "    1287:1287:int ?(int,int) -> ?";
+    ClassNameMapper.mapperFromString(mapping);
 
+    // From some other proguard generated map
+    mapping = "com.moat.analytics.mobile.cha.b -> com.moat.analytics.mobile.cha.b:\n"
+        + "    com.moat.analytics.mobile.cha.MoatAdEventType[] ? -> ?\n"
+        + "    java.util.HashMap ? -> ?\n"
+        + "    java.util.HashSet ?? -> ??\n";
     ClassNameMapper.mapperFromString(mapping);
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
index f3746fa..fea123d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
index a690bce..1609bfd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
@@ -7,7 +7,6 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
index 2157cd3..99707b6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
index 820afc2..74a4efe 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
index 1dd353e..49af47a 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
index 9ef14bf..dbae1da 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
index fac36d6..8430bb6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index b691046..fa76296 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
index 02b61f5..d034d84 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -7,7 +7,6 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
index 17cfb7f..044d21d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
index 5f87781..49a5910 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
index 6ed0099..49a1e14 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
index 0d2a486..d2e1cd0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
index 7484255..212e883 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
index f5733cc..01c0de9 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
index 9d2faf3..4cbd1c0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
index b957c64..5b1009e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
index e64abe1..8974d6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
index a5b13a7..c204e06 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
index 825262a..4d64e0e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index d7d45f0..a373627 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
index cf7f2ad..9c2c387 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
index 1474ff8..e4b9ed6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
index 18123a3..72b6d17 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
index 819e11e..c36ddd7 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
index 967dbc5..60cbd65 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
index dc51fa1..59d8dd2 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
index be844e3..15e8195 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
index fd38ee7..6f2d721 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
index a1fc427..a765f30 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
index 3ba2ce7..61b4154 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
index 8427e6a..48ef889 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
index e093f00..de515d0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
index 789e40c..e02b4cd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
index b77b29e..2a53f1b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
index 235bb56..779c140 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
index 80573ff..7ca8334 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
index 4f04aed..0a48ba2 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
index f548035..977de65 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
new file mode 100644
index 0000000..7d5381a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -0,0 +1,47 @@
+// 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.codeinspector;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.function.Predicate;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class CodeMatchers {
+
+  public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) {
+    if (!targetSubject.isPresent()) {
+      throw new IllegalArgumentException();
+    }
+    DexMethod target = targetSubject.getMethod().method;
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        if (!subject.isPresent()) {
+          return false;
+        }
+        if (!subject.getMethod().hasCode()) {
+          return false;
+        }
+        return subject.streamInstructions().anyMatch(isInvokeWithTarget(target));
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(" does not invoke `" + target.toSourceString() + "`");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
+  public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) {
+    return instruction -> instruction.isInvokeStatic() && instruction.getMethod() == target;
+  }
+}
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index 6013e6f..fa46485 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -8,7 +8,6 @@
 import optparse
 import os
 import shutil
-import subprocess
 import sys
 import utils
 
@@ -32,6 +31,9 @@
   parser.add_option('--adb-options',
                     help='additional adb options when running adb',
                     default=None)
+  parser.add_option('--quiet',
+                    help='disable verbose logging',
+                    default=False)
   (options, args) = parser.parse_args()
   if len(args) != 1:
     parser.error('Expected <apk> argument, got: ' + ' '.join(args))
@@ -41,44 +43,44 @@
 def findKeystore():
   return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
 
-def repack(processed_out, original_apk, temp):
+def repack(processed_out, original_apk, temp, quiet):
   processed_apk = os.path.join(temp, 'processed.apk')
   shutil.copyfile(original_apk, processed_apk)
   if not processed_out:
-    print 'Using original APK as is'
+    utils.Print('Using original APK as is', quiet=quiet)
     return processed_apk
-  print 'Repacking APK with dex files from', processed_apk
-  with utils.ChangedWorkingDirectory(temp):
+  utils.Print(
+      'Repacking APK with dex files from {}'.format(processed_apk), quiet=quiet)
+  with utils.ChangedWorkingDirectory(temp, quiet=quiet):
     cmd = ['zip', '-d', 'processed.apk', '*.dex']
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd)
+    utils.RunCmd(cmd, quiet=quiet)
   if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
     cmd = ['unzip', processed_out, '-d', temp]
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd)
+    if quiet:
+      cmd.insert(1, '-q')
+    utils.RunCmd(cmd, quiet=quiet)
     processed_out = temp
-  with utils.ChangedWorkingDirectory(processed_out):
+  with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
     dex = glob.glob('*.dex')
     cmd = ['zip', '-u', '-9', processed_apk] + dex
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd)
+    utils.RunCmd(cmd, quiet=quiet)
   return processed_apk
 
-def sign(unsigned_apk, keystore, temp):
+def sign(unsigned_apk, keystore, temp, quiet):
   signed_apk = os.path.join(temp, 'unaligned.apk')
-  apk_utils.sign(unsigned_apk, signed_apk, keystore)
+  apk_utils.sign(unsigned_apk, signed_apk, keystore, quiet=quiet)
   return signed_apk
 
-def align(signed_apk, temp):
-  print 'Aligning'
+def align(signed_apk, temp, quiet):
+  utils.Print('Aligning', quiet=quiet)
   aligned_apk = os.path.join(temp, 'aligned.apk')
   cmd = ['zipalign', '-f', '4', signed_apk, aligned_apk]
-  print ' '.join(cmd)
-  subprocess.check_call(cmd)
+  utils.RunCmd(cmd, quiet=quiet)
   return signed_apk
 
 def masseur(
-    apk, dex=None, out=None, adb_options=None, keystore=None, install=False):
+    apk, dex=None, out=None, adb_options=None, keystore=None, install=False,
+    quiet=False):
   if not out:
     out = os.path.basename(apk)
   if not keystore:
@@ -86,23 +88,23 @@
   with utils.TempDir() as temp:
     processed_apk = None
     if dex:
-      processed_apk = repack(dex, apk, temp)
+      processed_apk = repack(dex, apk, temp, quiet)
     else:
-      print 'Signing original APK without modifying dex files'
+      utils.Print(
+          'Signing original APK without modifying dex files', quiet=quiet)
       processed_apk = os.path.join(temp, 'processed.apk')
       shutil.copyfile(apk, processed_apk)
-    signed_apk = sign(processed_apk, keystore, temp)
-    aligned_apk = align(signed_apk, temp)
-    print 'Writing result to', out
+    signed_apk = sign(processed_apk, keystore, temp, quiet=quiet)
+    aligned_apk = align(signed_apk, temp, quiet=quiet)
+    utils.Print('Writing result to {}'.format(out), quiet=quiet)
     shutil.copyfile(aligned_apk, out)
-    adb_cmd = ['adb']
-    if adb_options:
-      adb_cmd.extend(
-          [option for option in adb_options.split(' ') if option])
     if install:
+      adb_cmd = ['adb']
+      if adb_options:
+        adb_cmd.extend(
+            [option for option in adb_options.split(' ') if option])
       adb_cmd.extend(['install', '-t', '-r', '-d', out]);
-      utils.PrintCmd(adb_cmd)
-      subprocess.check_call(adb_cmd)
+      utils.RunCmd(adb_cmd, quiet=quiet)
 
 def main():
   (options, apk) = parse_options()
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index aaae1e4..5a6aa94 100644
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -7,11 +7,10 @@
 import subprocess
 import utils
 
-def sign(unsigned_apk, signed_apk, keystore):
-  print 'Signing (ignore the warnings)'
+def sign(unsigned_apk, signed_apk, keystore, quiet=False):
+  utils.Print('Signing (ignore the warnings)', quiet=quiet)
   cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
-  utils.PrintCmd(cmd)
-  subprocess.call(cmd)
+  utils.RunCmd(cmd, quiet=quiet)
   cmd = [
     'jarsigner',
     '-sigalg', 'SHA1withRSA',
@@ -22,8 +21,7 @@
     unsigned_apk,
     'androiddebugkey'
   ]
-  utils.PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  utils.RunCmd(cmd, quiet=quiet)
 
 def sign_with_apksigner(build_tools_dir, unsigned_apk, signed_apk, keystore, password):
   cmd = [
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 9c2c8c8..10fb29f 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -30,23 +30,15 @@
             'Unexpected line with \'dependencies {\'')
         is_inside_dependencies = True
       if is_inside_dependencies:
-        if '/r8.jar' in stripped:
-          if minified:
-            # Skip line to avoid dependency on r8.jar
-            continue
-          added_r8_dependency = True
-        elif '/r8lib.jar' in stripped:
-          if not minified:
-            # Skip line to avoid dependency on r8lib.jar
-            continue
-          added_r8_dependency = True
+        if '/r8.jar' in stripped or '/r8lib.jar' in stripped:
+          # Skip line to avoid dependency on r8.jar
+          continue
         elif 'com.android.tools.build:gradle:' in stripped:
           gradle_version = stripped[stripped.rindex(':')+1:-1]
-          if not added_r8_dependency:
-            indent = ''.ljust(line.index('classpath'))
-            jar = os.path.join(temp_dir, 'r8lib.jar' if minified else 'r8.jar')
-            f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
-            added_r8_dependency = True
+          indent = ''.ljust(line.index('classpath'))
+          jar = os.path.join(temp_dir, 'r8lib.jar' if minified else 'r8.jar')
+          f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
+          added_r8_dependency = True
         elif stripped == '}':
           is_inside_dependencies = False
       f.write(line)
@@ -147,8 +139,9 @@
   # one of the predefined locations.
   assert False
 
-def Move(src, dst):
-  print('Moving `{}` to `{}`'.format(src, dst))
+def Move(src, dst, quiet=False):
+  if not quiet:
+    print('Moving `{}` to `{}`'.format(src, dst))
   dst_parent = os.path.dirname(dst)
   if not os.path.isdir(dst_parent):
     os.makedirs(dst_parent)
@@ -158,15 +151,15 @@
     os.remove(dst)
   os.rename(src, dst)
 
-def MoveDir(src, dst):
+def MoveDir(src, dst, quiet=False):
   assert os.path.isdir(src)
-  Move(src, dst)
+  Move(src, dst, quiet=quiet)
 
-def MoveFile(src, dst):
+def MoveFile(src, dst, quiet=False):
   assert os.path.isfile(src)
-  Move(src, dst)
+  Move(src, dst, quiet=quiet)
 
-def MoveProfileReportTo(dest_dir, build_stdout):
+def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
   html_file = None
   profile_message = 'See the profiling report at: '
   for line in build_stdout:
@@ -181,11 +174,12 @@
 
   assert os.path.isfile(html_file), 'Expected to find HTML file at {}'.format(
       html_file)
-  MoveFile(html_file, os.path.join(dest_dir, 'index.html'))
+  MoveFile(html_file, os.path.join(dest_dir, 'index.html'), quiet=quiet)
 
   html_dir = os.path.dirname(html_file)
   for dir_name in ['css', 'js']:
-    MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name))
+    MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name),
+        quiet=quiet)
 
 def ParseProfileReport(profile_dir):
   html_file = os.path.join(profile_dir, 'index.html')
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 128b6e3..e084bad 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -42,6 +42,8 @@
       'app_id': 'de.danoeh.antennapod',
       'git_repo': 'https://github.com/christofferqa/AntennaPod.git',
       'flavor': 'play',
+      'min_sdk': 14,
+      'compile_sdk': 26
   },
   'apps-android-wikipedia': {
       'app_id': 'org.wikipedia',
@@ -49,6 +51,10 @@
       'flavor': 'prod',
       'signed-apk-name': 'app-prod-universal-release.apk'
   },
+  'chanu': {
+    'app_id': 'com.chanapps.four.activity',
+    'git_repo': 'https://github.com/mkj-gram/chanu.git',
+  },
   'friendlyeats-android': {
       'app_id': 'com.google.firebase.example.fireeats',
       'git_repo': 'https://github.com/christofferqa/friendlyeats-android.git'
@@ -69,6 +75,14 @@
       'app_id': 'org.schabi.newpipe',
       'git_repo': 'https://github.com/christofferqa/NewPipe',
   },
+  'Signal-Android': {
+    'app_id': 'org.thoughtcrime.securesms',
+    'app_module': '',
+    'flavor': 'play',
+    'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
+    'releaseTarget': 'assemblePlayRelease',
+    'signed-apk-name': 'Signal-play-release-4.32.7.apk',
+  },
   'Simple-Calendar': {
       'app_id': 'com.simplemobiletools.calendar.pro',
       'git_repo': 'https://github.com/christofferqa/Simple-Calendar',
@@ -89,18 +103,6 @@
       # TODO(123047413): Fails with R8.
       'skip': True,
   },
-  'Signal-Android': {
-    'app_id': 'org.thoughtcrime.securesms',
-    'app_module': '',
-    'flavor': 'play',
-    'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
-    'releaseTarget': 'assemblePlayRelease',
-    'signed-apk-name': 'Signal-play-release-4.32.7.apk',
-  },
-  'chanu': {
-    'app_id': 'com.chanapps.four.activity',
-    'git_repo': 'https://github.com/mkj-gram/chanu.git',
-  },
   # This does not build yet.
   'muzei': {
       'git_repo': 'https://github.com/sgjesse/muzei.git',
@@ -128,7 +130,7 @@
       dex_size += z.getinfo(filename).file_size
   return dex_size
 
-def IsBuiltWithR8(apk, temp_dir):
+def IsBuiltWithR8(apk, temp_dir, options):
   r8_jar = os.path.join(temp_dir, 'r8.jar')
 
   # Use the copy of r8.jar if it is there.
@@ -138,7 +140,7 @@
     script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
     cmd = ['python', script, apk]
 
-  utils.PrintCmd(cmd)
+  utils.PrintCmd(cmd, quiet=options.quiet)
   return '~~R8' in subprocess.check_output(cmd).strip()
 
 def IsMinifiedR8(shrinker):
@@ -157,9 +159,13 @@
 def GitCheckout(file):
   return subprocess.check_output(['git', 'checkout', file]).strip()
 
-def InstallApkOnEmulator(apk_dest):
-  subprocess.check_call(
-      ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest])
+def InstallApkOnEmulator(apk_dest, options):
+  cmd = ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest]
+  if options.quiet:
+    with open(os.devnull, 'w') as devnull:
+      subprocess.check_call(cmd, stdout=devnull)
+  else:
+    subprocess.check_call(cmd)
 
 def PercentageDiffAsString(before, after):
   if after < before:
@@ -167,7 +173,7 @@
   else:
     return '+' + str(round((after - before) / before * 100)) + '%'
 
-def UninstallApkOnEmulator(app, config):
+def UninstallApkOnEmulator(app, config, options):
   app_id = config.get('app_id')
   process = subprocess.Popen(
       ['adb', 'uninstall', app_id],
@@ -215,10 +221,10 @@
   result = {}
 
   if not os.path.exists(checkout_dir):
-    with utils.ChangedWorkingDirectory(WORKING_DIR):
+    with utils.ChangedWorkingDirectory(WORKING_DIR, quiet=options.quiet):
       GitClone(git_repo)
   elif options.pull:
-    with utils.ChangedWorkingDirectory(checkout_dir):
+    with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
       # Checkout build.gradle to avoid merge conflicts.
       if IsTrackedByGit('build.gradle'):
         GitCheckout('build.gradle')
@@ -240,7 +246,7 @@
 def BuildAppWithSelectedShrinkers(app, config, options, checkout_dir, temp_dir):
   result_per_shrinker = {}
 
-  with utils.ChangedWorkingDirectory(checkout_dir):
+  with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
     for shrinker in SHRINKERS:
       if options.shrinker and shrinker not in options.shrinker:
         continue
@@ -280,15 +286,9 @@
           # Build app with gradle using -D...keepRuleSynthesisForRecompilation=
           # true.
           out_dir = os.path.join(checkout_dir, 'out', shrinker + '-1')
-          extra_env_vars = {
-            'JAVA_OPTS': ' '.join([
-              '-ea:com.android.tools.r8...',
-              '-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true'
-            ])
-          }
           (apk_dest, profile_dest_dir, ext_proguard_config_file) = \
               BuildAppWithShrinker(app, config, shrinker, checkout_dir, out_dir,
-                  temp_dir, options, extra_env_vars)
+                  temp_dir, options, keepRuleSynthesisForRecompilation=True)
           dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
           recompilation_result = {
             'apk_dest': apk_dest,
@@ -314,8 +314,9 @@
               recompiled_apk_dest = os.path.join(
                   checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
               RebuildAppWithShrinker(
-                  previous_apk, recompiled_apk_dest, ext_proguard_config_file,
-                  shrinker, min_sdk, compile_sdk, temp_dir)
+                  app, previous_apk, recompiled_apk_dest,
+                  ext_proguard_config_file, shrinker, min_sdk, compile_sdk,
+                  options, temp_dir)
               recompilation_result = {
                 'apk_dest': recompiled_apk_dest,
                 'build_status': 'success',
@@ -334,13 +335,20 @@
 
       result_per_shrinker[shrinker] = result
 
+  if not options.app:
+    print('')
+    LogResultsForApp(app, result_per_shrinker, options)
+    print('')
+
   return result_per_shrinker
 
 def BuildAppWithShrinker(
     app, config, shrinker, checkout_dir, out_dir, temp_dir, options,
-    env_vars=None):
-  print()
-  print('Building {} with {}'.format(app, shrinker))
+    keepRuleSynthesisForRecompilation=False):
+  print('Building {} with {}{}'.format(
+      app,
+      shrinker,
+      ' for recompilation' if keepRuleSynthesisForRecompilation else ''))
 
   # Add/remove 'r8.jar' from top-level build.gradle.
   if options.disable_tot:
@@ -361,11 +369,9 @@
   as_utils.SetPrintConfigurationDirective(
       app, config, checkout_dir, proguard_config_dest)
 
-  env = os.environ.copy()
+  env = {}
   env['ANDROID_HOME'] = android_home
   env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
-  if env_vars:
-    env.update(env_vars)
 
   releaseTarget = config.get('releaseTarget')
   if not releaseTarget:
@@ -381,20 +387,12 @@
          '--profile', '--stacktrace',
          '-Pandroid.enableR8=' + str(enableR8).lower(),
          '-Pandroid.enableR8.fullMode=' + str(enableR8FullMode).lower()]
+  if keepRuleSynthesisForRecompilation:
+    cmd.append('-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true')
   if options.gradle_flags:
     cmd.extend(options.gradle_flags.split(' '))
 
-  utils.PrintCmd(cmd)
-  build_process = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
-  stdout = []
-  while True:
-    line = build_process.stdout.readline()
-    if line != b'':
-      stripped = line.rstrip()
-      stdout.append(stripped)
-      print(stripped)
-    else:
-      break
+  stdout = utils.RunCmd(cmd, env, quiet=options.quiet)
 
   apk_base_name = (archives_base_name
       + (('-' + flavor) if flavor else '') + '-release')
@@ -425,25 +423,27 @@
 
   if os.path.isfile(signed_apk):
     apk_dest = os.path.join(out_dir, signed_apk_name)
-    as_utils.MoveFile(signed_apk, apk_dest)
+    as_utils.MoveFile(signed_apk, apk_dest, quiet=options.quiet)
   else:
     apk_dest = os.path.join(out_dir, unsigned_apk_name)
-    as_utils.MoveFile(unsigned_apk, apk_dest)
+    as_utils.MoveFile(unsigned_apk, apk_dest, quiet=options.quiet)
 
-  assert IsBuiltWithR8(apk_dest, temp_dir) == ('r8' in shrinker), (
+  assert IsBuiltWithR8(apk_dest, temp_dir, options) == ('r8' in shrinker), (
       'Unexpected marker in generated APK for {}'.format(shrinker))
 
   profile_dest_dir = os.path.join(out_dir, 'profile')
-  as_utils.MoveProfileReportTo(profile_dest_dir, stdout)
+  as_utils.MoveProfileReportTo(profile_dest_dir, stdout, quiet=options.quiet)
 
   return (apk_dest, profile_dest_dir, proguard_config_dest)
 
 def RebuildAppWithShrinker(
-    apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
-    temp_dir):
+    app, apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
+    options, temp_dir):
   assert 'r8' in shrinker
   assert apk_dest.endswith('.apk')
 
+  print('Rebuilding {} with {}'.format(app, shrinker))
+
   # Compile given APK with shrinker to temporary zip file.
   android_jar = utils.get_android_jar(compile_sdk)
   r8_jar = os.path.join(
@@ -462,20 +462,19 @@
     cmd.append('--lib')
     cmd.append(android_optional_jar)
 
-  utils.PrintCmd(cmd)
-
-  subprocess.check_output(cmd)
+  utils.RunCmd(cmd, quiet=options.quiet)
 
   # Make a copy of the given APK, move the newly generated dex files into the
   # copied APK, and then sign the APK.
-  apk_masseur.masseur(apk, dex=zip_dest, out=apk_dest)
+  apk_masseur.masseur(
+    apk, dex=zip_dest, out=apk_dest, quiet=options.quiet)
 
 def RunMonkey(app, config, options, apk_dest):
   if not WaitForEmulator():
     return False
 
-  UninstallApkOnEmulator(app, config)
-  InstallApkOnEmulator(apk_dest)
+  UninstallApkOnEmulator(app, config, options)
+  InstallApkOnEmulator(apk_dest, options)
 
   app_id = config.get('app_id')
   number_of_events_to_generate = options.monkey_events
@@ -486,27 +485,28 @@
 
   cmd = ['adb', 'shell', 'monkey', '-p', app_id, '-s', str(random_seed),
       str(number_of_events_to_generate)]
-  utils.PrintCmd(cmd)
 
   try:
-    stdout = subprocess.check_output(cmd)
+    stdout = utils.RunCmd(cmd, quiet=options.quiet)
     succeeded = (
         'Events injected: {}'.format(number_of_events_to_generate) in stdout)
   except subprocess.CalledProcessError as e:
     succeeded = False
 
-  UninstallApkOnEmulator(app, config)
+  UninstallApkOnEmulator(app, config, options)
 
   return succeeded
 
 def LogResultsForApps(result_per_shrinker_per_app, options):
-  for app, result_per_shrinker in result_per_shrinker_per_app.iteritems():
+  print('')
+  for app, result_per_shrinker in sorted(
+      result_per_shrinker_per_app.iteritems()):
     LogResultsForApp(app, result_per_shrinker, options)
 
 def LogResultsForApp(app, result_per_shrinker, options):
   print(app + ':')
 
-  if result_per_shrinker.get('status') != 'success':
+  if result_per_shrinker.get('status', 'success') != 'success':
     error_message = result_per_shrinker.get('error_message')
     print('  skipped ({})'.format(error_message))
     return
@@ -613,6 +613,10 @@
                     action='store_true')
   result.add_option('--gradle-flags', '--gradle_flags',
                     help='Flags to pass in to gradle')
+  result.add_option('--quiet',
+                    help='Disable verbose logging',
+                    default=False,
+                    action='store_true')
   (options, args) = result.parse_args(argv)
   if options.disable_tot:
     # r8.jar is required for recompiling the generated APK
@@ -653,7 +657,7 @@
       result_per_shrinker_per_app[options.app] = GetResultsForApp(
           options.app, APPS.get(options.app), options, temp_dir)
     else:
-      for app, config in APPS.iteritems():
+      for app, config in sorted(APPS.iteritems()):
         if not config.get('skip', False):
           result_per_shrinker_per_app[app] = GetResultsForApp(
               app, config, options, temp_dir)
diff --git a/tools/utils.py b/tools/utils.py
index 72febdc..8c37a55 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -54,13 +54,86 @@
 R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
 RETRACE_JAR = os.path.join(THIRD_PARTY, 'proguard', 'proguard6.0.1', 'lib', 'retrace.jar')
 
-def PrintCmd(s):
-  if type(s) is list:
-    s = ' '.join(s)
-  print 'Running: %s' % s
+def Print(s, quiet=False):
+  if quiet:
+    return
+  print(s)
+
+def Warn(message):
+  CRED = '\033[91m'
+  CEND = '\033[0m'
+  print(CRED + message + CEND)
+
+def PrintCmd(cmd, env=None, quiet=False):
+  if quiet:
+    return
+  if type(cmd) is list:
+    cmd = ' '.join(cmd)
+  if env:
+    env = ' '.join(['{}=\"{}\"'.format(x, y) for x, y in env.iteritems()])
+    print('Running: {} {}'.format(env, cmd))
+  else:
+    print('Running: {}'.format(cmd))
   # I know this will hit os on windows eventually if we don't do this.
   sys.stdout.flush()
 
+class ProgressLogger(object):
+  CLEAR_LINE = '\033[K'
+  UP = '\033[F'
+
+  def __init__(self, quiet=False):
+    self._count = 0
+    self._has_printed = False
+    self._quiet = quiet
+
+  def log(self, text):
+    if self._quiet:
+      if self._has_printed:
+        sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+      if len(text) > 140:
+        text = text[0:140] + '...'
+    print(text)
+    self._has_printed = True
+
+  def done(self):
+    if self._quiet and self._has_printed:
+      sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+      print('')
+      sys.stdout.write(ProgressLogger.UP)
+
+def RunCmd(cmd, env_vars=None, quiet=False):
+  PrintCmd(cmd, env=env_vars, quiet=quiet)
+  env = os.environ.copy()
+  if env_vars:
+    env.update(env_vars)
+  process = subprocess.Popen(
+      cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+  stdout = []
+  logger = ProgressLogger(quiet=quiet)
+  failed = False
+  while True:
+    line = process.stdout.readline()
+    if line != b'':
+      stripped = line.rstrip()
+      stdout.append(stripped)
+      logger.log(stripped)
+
+      # TODO(christofferqa): r8 should fail with non-zero exit code.
+      if ('AssertionError' in stripped
+          or 'CompilationError' in stripped
+          or 'CompilationFailedException' in stripped
+          or 'Compilation failed' in stripped):
+        failed = True
+    else:
+      logger.done()
+      exit_code = process.poll()
+      if exit_code or failed:
+        for line in stdout:
+          Warn(line)
+        raise subprocess.CalledProcessError(
+            exit_code, cmd, output='\n'.join(stdout))
+      return stdout
+
 def IsWindows():
   return os.name == 'nt'
 
@@ -196,16 +269,19 @@
    shutil.rmtree(self._temp_dir, ignore_errors=True)
 
 class ChangedWorkingDirectory(object):
- def __init__(self, working_directory):
+ def __init__(self, working_directory, quiet=False):
+   self._quiet = quiet
    self._working_directory = working_directory
 
  def __enter__(self):
    self._old_cwd = os.getcwd()
-   print 'Enter directory = ', self._working_directory
+   if not self._quiet:
+     print 'Enter directory:', self._working_directory
    os.chdir(self._working_directory)
 
  def __exit__(self, *_):
-   print 'Enter directory = ', self._old_cwd
+   if not self._quiet:
+     print 'Enter directory:', self._old_cwd
    os.chdir(self._old_cwd)
 
 # Reading Android CTS test_result.xml