Merge "Make sure nullability in type lattice conforms to neverNull bit."
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 3521429..364a794 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -161,7 +161,7 @@
             String className = resource.getClassDescriptors().iterator().next();
             String entryName = getClassFileName(className);
             byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
-            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
+            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b5814d2..4d27b8a 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.2-dev";
+  public static final String LABEL = "1.5.3-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 6bebb0d..8c79ea1 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -243,7 +243,7 @@
             stackValue, constant.asConstString().getValue(), ThrowingInfo.NO_THROW);
       } else if (constant.isDexItemBasedConstString()) {
         return new DexItemBasedConstString(
-            stackValue, constant.asDexItemBasedConstString().getItem());
+            stackValue, constant.asDexItemBasedConstString().getItem(), ThrowingInfo.NO_THROW);
       } else if (constant.isConstClass()) {
         return new ConstClass(stackValue, constant.asConstClass().getValue());
       } else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index c38cf60..0467fcd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -49,7 +49,7 @@
 
   @Override
   public boolean canThrow() {
-    // The ldc instruction may throw in Java bytecode.
+    // const-string* may throw in dex. (Not in CF, see CfSourceCode.canThrowHelper)
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index f0c9636..8ba58b7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -57,7 +57,7 @@
 
   @Override
   public boolean canThrow() {
-    // The ldc instruction may throw in Java bytecode.
+    // const-string* may throw in dex. (Not in CF, see CfSourceCode.canThrowHelper)
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 755402d..2042392 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -267,7 +267,8 @@
             encodedMethod,
             graphLense.getOriginalMethodSignature(encodedMethod.method),
             callerPosition,
-            origin);
+            origin,
+            options.getInternalOutputMode());
     return new IRBuilder(encodedMethod, appInfo, source, options, origin, generator, graphLense)
         .build(context);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 04e9a1b..692dba2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -98,6 +98,9 @@
 
   @Override
   public boolean instructionInstanceCanThrow() {
+    if (throwingInfo == ThrowingInfo.NO_THROW) {
+      return false;
+    }
     // The const-string instruction can be a throwing instruction in DEX, if decode() fails.
     try {
       value.toString();
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index fa4e6c5..c3b8f80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 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.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
@@ -20,22 +21,27 @@
 
   private final DexReference item;
   private final ClassNameComputationInfo classNameComputationInfo;
+  private final ThrowingInfo throwingInfo;
 
-  public DexItemBasedConstString(Value dest, DexReference item) {
-    this(dest, item, ClassNameComputationInfo.none());
+  public DexItemBasedConstString(Value dest, DexReference item, ThrowingInfo throwingInfo) {
+    this(dest, item, throwingInfo, ClassNameComputationInfo.none());
   }
 
   public DexItemBasedConstString(
-      Value dest, DexReference item, ClassNameComputationInfo classNameComputationInfo) {
+      Value dest,
+      DexReference item,
+      ThrowingInfo throwingInfo,
+      ClassNameComputationInfo classNameComputationInfo) {
     super(dest);
     dest.markNeverNull();
     this.item = item;
     this.classNameComputationInfo = classNameComputationInfo;
+    this.throwingInfo = throwingInfo;
   }
 
   public static DexItemBasedConstString copyOf(Value newValue, DexItemBasedConstString original) {
     return new DexItemBasedConstString(
-        newValue, original.getItem(), original.classNameComputationInfo);
+        newValue, original.getItem(), original.throwingInfo, original.classNameComputationInfo);
   }
 
   public DexReference getItem() {
@@ -88,7 +94,7 @@
 
   @Override
   public boolean instructionTypeCanThrow() {
-    return true;
+    return throwingInfo == ThrowingInfo.CAN_THROW;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index a745659..2c10fbd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.conversion.CfState.Snapshot;
 import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOutputMode;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -196,13 +197,15 @@
   private Int2ObjectMap<DebugLocalInfo> outgoingLocals;
   private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
   private final CanonicalPositions canonicalPositions;
+  private final InternalOutputMode internalOutputMode;
 
   public CfSourceCode(
       CfCode code,
       DexEncodedMethod method,
       DexMethod originalMethod,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      InternalOutputMode internalOutputMode) {
     this.code = code;
     this.method = method;
     this.origin = origin;
@@ -218,6 +221,7 @@
     }
     this.state = new CfState(origin);
     canonicalPositions = new CanonicalPositions(callerPosition, cfPositionCount, originalMethod);
+    this.internalOutputMode = internalOutputMode;
   }
 
   @Override
@@ -249,8 +253,9 @@
   // Utility method that treats constant strings as not throwing in the case of having CF output.
   // This is the only instruction that differ in throwing between DEX and CF. If we find more
   // consider rewriting CfInstruction.canThrow to take in options.
-  private boolean canThrowHelper(IRBuilder builder, CfInstruction instruction) {
-    if (builder.isGeneratingClassFiles() && instruction.isConstString()) {
+  private boolean canThrowHelper(CfInstruction instruction) {
+    if (internalOutputMode.isGeneratingClassFiles()
+        && (instruction.isConstString() || instruction.isDexItemBasedConstString())) {
       return false;
     }
     return instruction.canThrow();
@@ -259,7 +264,8 @@
   @Override
   public int traceInstruction(int instructionIndex, IRBuilder builder) {
     CfInstruction instruction = code.getInstructions().get(instructionIndex);
-    if (canThrowHelper(builder, instruction)) {
+    assert builder.isGeneratingClassFiles() == internalOutputMode.isGeneratingClassFiles();
+    if (canThrowHelper(instruction)) {
       TryHandlerList tryHandlers = getTryHandlers(instructionIndex);
       if (!tryHandlers.isEmpty()) {
         // Ensure the block starts at the start of the try-range (don't enqueue, not a target).
@@ -433,7 +439,7 @@
     assert currentBlockInfo != null;
     setLocalVariableLists();
 
-    if (canThrowHelper(builder, instruction)) {
+    if (canThrowHelper(instruction)) {
       Snapshot exceptionTransfer =
           state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
       for (int target : currentBlockInfo.exceptionalSuccessors) {
@@ -632,7 +638,7 @@
 
   @Override
   public boolean verifyCurrentInstructionCanThrow() {
-    return code.getInstructions().get(currentInstructionIndex).canThrow();
+    return canThrowHelper(code.getInstructions().get(currentInstructionIndex));
   }
 
   @Override
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 37b3d1b..31b0ec6 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
@@ -1147,11 +1147,14 @@
     add(instruction);
   }
 
+  private ThrowingInfo throwingInfoForConstStrings() {
+    return options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+  }
+
   public void addConstString(int dest, DexString string) {
     TypeLatticeElement typeLattice =
         TypeLatticeElement.stringClassType(appInfo, definitelyNotNull());
-    ThrowingInfo throwingInfo =
-        options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+    ThrowingInfo throwingInfo = throwingInfoForConstStrings();
     add(new ConstString(writeRegister(dest, typeLattice, throwingInfo), string, throwingInfo));
   }
 
@@ -1159,8 +1162,9 @@
     assert method.getOptimizationInfo().useIdentifierNameString();
     TypeLatticeElement typeLattice =
         TypeLatticeElement.stringClassType(appInfo, definitelyNotNull());
-    Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
-    DexItemBasedConstString instruction = new DexItemBasedConstString(out, item);
+    ThrowingInfo throwingInfo = throwingInfoForConstStrings();
+    Value out = writeRegister(dest, typeLattice, throwingInfo);
+    DexItemBasedConstString instruction = new DexItemBasedConstString(out, item, throwingInfo);
     add(instruction);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 5d2daed..5bf9154 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -194,7 +194,7 @@
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
             : null;
-    this.stringOptimizer = new StringOptimizer(appInfo, options);
+    this.stringOptimizer = new StringOptimizer(appInfo, options.getInternalOutputMode());
     this.enableWholeProgramOptimizations = appView != null;
     if (enableWholeProgramOptimizations) {
       assert appInfo.hasLiveness();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 0ff9331..fd326d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -32,7 +32,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOutputMode;
 import com.google.common.annotations.VisibleForTesting;
 import java.util.Set;
 import java.util.function.BiFunction;
@@ -44,11 +44,11 @@
   private final DexItemFactory factory;
   private final ThrowingInfo throwingInfo;
 
-  public StringOptimizer(AppInfo appInfo, InternalOptions options) {
+  public StringOptimizer(AppInfo appInfo, InternalOutputMode outputMode) {
     this.appInfo = appInfo;
     this.factory = appInfo.dexItemFactory;
-    throwingInfo = options.isGeneratingClassFiles()
-        ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+    this.throwingInfo =
+        outputMode.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
   }
 
   // int String#length()
@@ -194,8 +194,12 @@
       String name = null;
       if (invokedMethod == factory.classMethods.getName) {
         if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
-          deferred = new DexItemBasedConstString(
-              invoke.outValue(), baseType, new ClassNameComputationInfo(NAME, arrayDepth));
+          deferred =
+              new DexItemBasedConstString(
+                  invoke.outValue(),
+                  baseType,
+                  throwingInfo,
+                  new ClassNameComputationInfo(NAME, arrayDepth));
         } else {
           name = computeClassName(descriptor, holder, NAME, arrayDepth);
         }
@@ -218,6 +222,7 @@
                 new DexItemBasedConstString(
                     invoke.outValue(),
                     baseType,
+                    throwingInfo,
                     new ClassNameComputationInfo(CANONICAL_NAME, arrayDepth));
           } else {
             name = computeClassName(descriptor, holder, CANONICAL_NAME, arrayDepth);
@@ -234,8 +239,12 @@
             continue;
           }
           if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
-            deferred = new DexItemBasedConstString(
-                invoke.outValue(), baseType, new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
+            deferred =
+                new DexItemBasedConstString(
+                    invoke.outValue(),
+                    baseType,
+                    throwingInfo,
+                    new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
           } else {
             name = computeClassName(descriptor, holder, SIMPLE_NAME, arrayDepth);
           }
@@ -313,8 +322,8 @@
               code.createValue(
                   TypeLatticeElement.stringClassType(appInfo, definitelyNotNull()),
                   invoke.getLocalInfo());
-          ConstString nullString = new ConstString(
-              nullStringValue, factory.createString("null"), throwingInfo);
+          ConstString nullString =
+              new ConstString(nullStringValue, factory.createString("null"), throwingInfo);
           it.replaceCurrentInstruction(nullString);
         } else if (inType.nullability().isDefinitelyNotNull()
             && inType.isClassType()
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 95fcfd2..0638816 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -87,6 +88,8 @@
     if (!code.hasConstString) {
       return;
     }
+    final ThrowingInfo throwingInfo =
+        options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.NO_THROW;
     DexType originHolder = code.method.method.getHolder();
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
@@ -131,7 +134,8 @@
           iterator.previous();
           // Prepare $decoupled just before $fieldPut
           Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
-          DexItemBasedConstString decoupled = new DexItemBasedConstString(newIn, itemBasedString);
+          DexItemBasedConstString decoupled =
+              new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
           decoupled.setPosition(fieldPut.getPosition());
           // If the current block has catch handler, split into two blocks.
           // Because const-string we're about to add is also a throwing instr, we need to split
@@ -191,7 +195,8 @@
             iterator.previous();
             // Prepare $decoupled just before $invoke
             Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
-            DexItemBasedConstString decoupled = new DexItemBasedConstString(newIn, itemBasedString);
+            DexItemBasedConstString decoupled =
+                new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
             decoupled.setPosition(invoke.getPosition());
             changes[positionOfIdentifier] = newIn;
             // If the current block has catch handler, split into two blocks.
@@ -238,7 +243,7 @@
               // Prepare $decoupled just before $invoke
               Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
               DexItemBasedConstString decoupled =
-                  new DexItemBasedConstString(newIn, itemBasedString);
+                  new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
               decoupled.setPosition(invoke.getPosition());
               changes[i] = newIn;
               // If the current block has catch handler, split into two blocks.
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 69d65ea..2276790 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -150,7 +150,7 @@
 
   private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
     try {
-      ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.STORED);
+      ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.DEFLATED);
     } catch (IOException e) {
       handleIOException(e, handler);
     }
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 31a76ab..84b6675 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -190,11 +190,11 @@
       }
     }
 
-    ProgramResource mergeClassFiles(List<ProgramResource> dexFiles, Path out) throws Throwable {
+    AndroidApp mergeClassFiles(List<ProgramResource> dexFiles, Path out) throws Throwable {
       return mergeClassFiles(dexFiles, out, OutputMode.DexIndexed);
     }
 
-    ProgramResource mergeClassFiles(
+    AndroidApp mergeClassFiles(
         List<ProgramResource> dexFiles, Path outputPath, OutputMode outputMode) throws Throwable {
       Builder builder = D8Command.builder();
       for (ProgramResource dexFile : dexFiles) {
@@ -215,7 +215,7 @@
       try {
         AndroidApp app = ToolHelper.runD8(builder, this::combinedOptionConsumer);
         assert app.getDexProgramResourcesForTesting().size() == 1;
-        return app.getDexProgramResourcesForTesting().get(0);
+        return app;
       } catch (Unimplemented | CompilationError | InternalCompilerError re) {
         throw re;
       } catch (RuntimeException re) {
@@ -249,13 +249,16 @@
       Assert.assertArrayEquals(readResource(entry.getValue()), readResource(otherResource));
     }
 
-    ProgramResource mergedFromCompiledSeparately =
+    AndroidApp mergedFromCompiledSeparately =
         test.mergeClassFiles(Lists.newArrayList(compiledSeparately.values()), null);
-    ProgramResource mergedFromCompiledTogether =
+    AndroidApp mergedFromCompiledTogether =
         test.mergeClassFiles(Lists.newArrayList(compiledTogether.values()), null);
+
+    // TODO(b/123504206): Add a main method and test the output runs.
+
     Assert.assertArrayEquals(
-        readResource(mergedFromCompiledSeparately),
-        readResource(mergedFromCompiledTogether));
+        readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+        readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
   }
 
   @Test
@@ -269,16 +272,24 @@
     D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
     test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
 
-    ProgramResource mergedFromCompiledSeparately =
+    AndroidApp mergedFromCompiledSeparately =
         test.mergeClassFiles(
             Lists.newArrayList(test.compileClassesSeparately(inputJarFile).values()), null);
-    ProgramResource mergedFromCompiledTogether =
+    AndroidApp mergedFromCompiledTogether =
         test.mergeClassFiles(
             Lists.newArrayList(test.compileClassesTogether(inputJarFile, null).values()), null);
 
+    Path out1 = temp.newFolder().toPath().resolve("out-together.zip");
+    mergedFromCompiledTogether.writeToZip(out1, OutputMode.DexIndexed);
+    ToolHelper.runArtNoVerificationErrors(out1.toString(), testPackage + "." + mainClass);
+
+    Path out2 = temp.newFolder().toPath().resolve("out-separate.zip");
+    mergedFromCompiledSeparately.writeToZip(out2, OutputMode.DexIndexed);
+    ToolHelper.runArtNoVerificationErrors(out2.toString(), testPackage + "." + mainClass);
+
     Assert.assertArrayEquals(
-        readResource(mergedFromCompiledSeparately),
-        readResource(mergedFromCompiledTogether));
+        readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+        readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
   }
 
   @Test
@@ -292,16 +303,19 @@
     D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
     test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
 
-    ProgramResource mergedFromCompiledSeparately =
+    AndroidApp mergedFromCompiledSeparately =
         test.mergeClassFiles(
             Lists.newArrayList(test.compileClassesSeparately(inputJarFile).values()), null);
-    ProgramResource mergedFromCompiledTogether =
+    AndroidApp mergedFromCompiledTogether =
         test.mergeClassFiles(
             Lists.newArrayList(test.compileClassesTogether(inputJarFile, null).values()), null);
 
+    // TODO(b/123504206): This test throws an index out of bounds exception.
+    // Re-write or verify running fails in the expected way.
+
     Assert.assertArrayEquals(
-        readResource(mergedFromCompiledSeparately),
-        readResource(mergedFromCompiledTogether));
+        readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+        readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1df6808..7493eba 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -903,18 +903,22 @@
     return extractor.getClassInternalType();
   }
 
-  protected Path writeToJar(List<byte[]> classes) throws IOException {
-    Path result = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
+  protected static void writeToJar(Path output, List<byte[]> classes) throws IOException {
     try (ZipOutputStream out =
         new ZipOutputStream(
             Files.newOutputStream(
-                result, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
+                output, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
       for (byte[] clazz : classes) {
         String name = extractClassName(clazz);
         ZipUtils.writeToZipStream(
             out, DescriptorUtils.getPathFromJavaType(name), clazz, ZipEntry.STORED);
       }
     }
+  }
+
+  protected Path writeToJar(List<byte[]> classes) throws IOException {
+    Path result = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
+    writeToJar(result, classes);
     return result;
   }
 
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index fb61f45..1c0287e 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -50,6 +51,7 @@
 import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
 import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ObjectReferenceCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ReferenceTypeCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.StackFrameCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
@@ -153,6 +155,12 @@
   }
 
   protected final void runDebugTest(
+      DebugTestConfig config, Class<?> debuggeeClass, JUnit3Wrapper.Command... commands)
+      throws Throwable {
+    runInternal(config, debuggeeClass.getTypeName(), Arrays.asList(commands));
+  }
+
+  protected final void runDebugTest(
       DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
       throws Throwable {
     runInternal(config, debuggeeClass, Arrays.asList(commands));
@@ -324,6 +332,10 @@
     return new JUnit3Wrapper.Command.RunCommand();
   }
 
+  protected final JUnit3Wrapper.Command breakpoint(MethodReference method) {
+    return breakpoint(method.getHolderClass().getTypeName(), method.getMethodName());
+  }
+
   protected final JUnit3Wrapper.Command breakpoint(String className, String methodName) {
     return breakpoint(className, methodName, null);
   }
@@ -477,6 +489,10 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkLine(int line) {
+    return inspect(t -> t.checkLine(null, line));
+  }
+
   protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
     return inspect(t -> t.checkLine(sourceFile, line));
   }
@@ -532,6 +548,16 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkFieldOnThis(
+      String fieldName, String fieldSignature, Value expectedValue) {
+    return inspect(
+        t -> {
+          Value value = t.getFieldOnThis(fieldName, fieldSignature);
+          Assert.assertEquals(
+              "Incorrect value for field 'this." + fieldName + "'", expectedValue, value);
+        });
+  }
+
   protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
     return t -> inspector.accept(t.debuggeeState);
   }
@@ -1298,6 +1324,7 @@
 
         @Override
         public void checkLine(String sourceFile, int line) {
+          sourceFile = sourceFile != null ? sourceFile : getSourceFile();
           if (!Objects.equals(sourceFile, getSourceFile()) || line != getLineNumber()) {
             String locationString = convertCurrentLocationToString();
             Assert.fail(
@@ -1501,7 +1528,15 @@
 
         // The class is available, lookup and read the field.
         long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
-        return getField(getMirror(), classId, fieldId);
+        return internalStaticField(getMirror(), classId, fieldId);
+      }
+
+      public Value getFieldOnThis(String fieldName, String fieldSignature) {
+        long thisObjectId = getMirror().getThisObject(getThreadId(), getFrameId());
+        long classId = getMirror().getReferenceType(thisObjectId);
+        // TODO(zerny): Search supers too. This will only get the field if directly on the class.
+        long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+        return internalInstanceField(getMirror(), thisObjectId, fieldId);
       }
 
       private long findField(VmMirror mirror, long classId, String fieldName,
@@ -1547,10 +1582,10 @@
         return matchingFieldIds.getLong(0);
       }
 
-      private Value getField(VmMirror mirror, long classId, long fieldId) {
-
-        CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
-            ReferenceTypeCommandSet.GetValuesCommand);
+      private static Value internalStaticField(VmMirror mirror, long classId, long fieldId) {
+        CommandPacket commandPacket =
+            new CommandPacket(
+                ReferenceTypeCommandSet.CommandSetID, ReferenceTypeCommandSet.GetValuesCommand);
         commandPacket.setNextValueAsReferenceTypeID(classId);
         commandPacket.setNextValueAsInt(1);
         commandPacket.setNextValueAsFieldID(fieldId);
@@ -1565,6 +1600,23 @@
       }
     }
 
+    private static Value internalInstanceField(VmMirror mirror, long objectId, long fieldId) {
+      CommandPacket commandPacket =
+          new CommandPacket(
+              ObjectReferenceCommandSet.CommandSetID, ObjectReferenceCommandSet.GetValuesCommand);
+      commandPacket.setNextValueAsObjectID(objectId);
+      commandPacket.setNextValueAsInt(1);
+      commandPacket.setNextValueAsFieldID(fieldId);
+      ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+      assert replyPacket.getErrorCode() == Error.NONE;
+
+      int fieldsCount = replyPacket.getNextValueAsInt();
+      assert fieldsCount == 1;
+      Value result = replyPacket.getNextValueAsValue();
+      Assert.assertTrue(replyPacket.isAllDataRead());
+      return result;
+    }
+
     public static Optional<Variable> getVariableAt(VmMirror mirror, Location location,
         String localName) {
       return getVariablesAt(mirror, location).stream()
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 17d5077..c398e8b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -255,7 +255,8 @@
 
   @Test
   public void testRegress62300145() throws Exception {
-    testDebugging("regress_62300145", "Regress");
+    // TODO(b/67936230): Executes differently for Java 8 and 9, so don't compare to DEX output.
+    testDebuggingJvmOnly("regress_62300145", "Regress");
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
new file mode 100644
index 0000000..9991b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
@@ -0,0 +1,32 @@
+// 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.debug;
+
+public class LambdaOuterContextTest {
+
+  public interface Converter {
+    String convert(int value);
+  }
+
+  public int outer;
+
+  public LambdaOuterContextTest(int outer) {
+    this.outer = outer;
+  }
+
+  public void foo(Converter converter) {
+    System.out.println(converter.convert(outer));
+  }
+
+  public void bar(int arg) {
+    foo(value -> {
+      // Ensure lambda uses parts of the outer context, otherwise javac will optimize it out.
+      return Integer.toString(outer + value + arg);
+    });
+  }
+
+  public static void main(String[] args) {
+    new LambdaOuterContextTest(args.length + 42).bar(args.length);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
new file mode 100644
index 0000000..8af2a6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -0,0 +1,67 @@
+// 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.debug;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.debug.LambdaOuterContextTest.Converter;
+import com.android.tools.r8.utils.StringUtils;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class LambdaOuterContextTestRunner extends DebugTestBase {
+
+  public static final Class<?> CLASS = LambdaOuterContextTest.class;
+  public static final String EXPECTED = StringUtils.lines("84");
+
+  @Test
+  public void testJvm() throws Throwable {
+    JvmTestBuilder jvmTestBuilder = testForJvm().addTestClasspath();
+    jvmTestBuilder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(jvmTestBuilder.debugConfig());
+  }
+
+  @Test
+  @Ignore("b/123068053")
+  public void testD8() throws Throwable {
+    D8TestCompileResult compileResult =
+        testForD8().addProgramClassesAndInnerClasses(CLASS).compile();
+    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(compileResult.debugConfig());
+  }
+
+  @Test
+  public void testR8Cf() throws Throwable {
+    R8TestCompileResult compileResult =
+        testForR8(Backend.CF)
+            .addProgramClassesAndInnerClasses(CLASS)
+            .debug()
+            .noMinification()
+            .noTreeShaking()
+            .compile();
+    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(compileResult.debugConfig());
+  }
+
+  private void runDebugger(DebugTestConfig config) throws Throwable {
+    runDebugTest(
+        config,
+        CLASS,
+        breakpoint(methodFromMethod(CLASS.getMethod("foo", Converter.class))),
+        run(),
+        checkLine(19),
+        checkLocals("this", "converter"),
+        checkFieldOnThis("outer", null, Value.createInt(42)),
+        stepInto(INTELLIJ_FILTER),
+        checkLine(25),
+        checkLocals("this", "value", "arg"),
+        checkNoLocal("outer"),
+        checkFieldOnThis("outer", null, Value.createInt(42)),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index 1b413ba..f9e6c34 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -13,7 +13,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-// TODO(shertz) test local variables
 @RunWith(Parameterized.class)
 public class LambdaTest extends DebugTestBase {
 
@@ -47,8 +46,11 @@
         run(),
         checkMethod(debuggeeClass, initialMethodName),
         checkLine(SOURCE_FILE, 12),
+        checkLocals("i"),
+        checkNoLocal("j"),
         stepInto(INTELLIJ_FILTER),
         checkLine(SOURCE_FILE, 16),
+        checkLocals("i", "j"),
         run());
   }
 
@@ -63,8 +65,10 @@
         run(),
         checkMethod(debuggeeClass, initialMethodName),
         checkLine(SOURCE_FILE, 32),
+        checkLocals("i", "a", "b"),
         stepInto(INTELLIJ_FILTER),
         checkLine(SOURCE_FILE, 37),
+        checkLocals("a", "b"),
         run());
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTest.java
new file mode 100644
index 0000000..0da8519
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTest.java
@@ -0,0 +1,404 @@
+// 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 org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+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;
+
+class LibraryClass {
+  static String LIB_MSG = "LibraryClass::foo";
+  void foo() {
+    System.out.println(LIB_MSG);
+  }
+}
+
+class AnotherLibraryClass {
+  static String ANOTHERLIB_MSG = "AnotherLibraryClass::foo";
+  void foo() {
+    System.out.println(ANOTHERLIB_MSG);
+  }
+}
+
+class ProgramClass extends LibraryClass {
+  static String PRG_MSG = "ProgramClass::bar";
+  void bar() {
+    System.out.println(PRG_MSG);
+  }
+
+  public static void main(String[] args) {
+    new AnotherLibraryClass().foo();
+    ProgramClass instance = new ProgramClass();
+    instance.foo();
+    instance.bar();
+  }
+}
+
+public class NameClashTest extends TestBase {
+
+  @ClassRule
+  public static TemporaryFolder temporaryFolder = ToolHelper.getTemporaryFolderForTest();
+
+  private static Class<?> MAIN = ProgramClass.class;
+  private static String EXPECTED_OUTPUT =
+      StringUtils.lines(
+          AnotherLibraryClass.ANOTHERLIB_MSG, LibraryClass.LIB_MSG, ProgramClass.PRG_MSG);
+
+  private static Path prgJarThatUsesOriginalLib;
+  private static Path prgJarThatUsesMinifiedLib;
+  private static Path libJar;
+  private Path mappingFile;
+
+  @BeforeClass
+  public static void setUpJars() throws Exception {
+    prgJarThatUsesOriginalLib =
+        temporaryFolder.newFile("prgOrginalLib.jar").toPath().toAbsolutePath();
+    writeToJar(prgJarThatUsesOriginalLib, ImmutableList.of(ToolHelper.getClassAsBytes(MAIN)));
+    prgJarThatUsesMinifiedLib =
+        temporaryFolder.newFile("prgMinifiedLib.jar").toPath().toAbsolutePath();
+    writeToJar(prgJarThatUsesMinifiedLib, ImmutableList.of(ProgramClassDump.dump()));
+    libJar = temporaryFolder.newFile("lib.jar").toPath().toAbsolutePath();
+    writeToJar(libJar, ImmutableList.of(
+        ToolHelper.getClassAsBytes(LibraryClass.class),
+        ToolHelper.getClassAsBytes(AnotherLibraryClass.class)));
+  }
+
+  @Before
+  public void setUpMappingFile() throws Exception {
+    mappingFile = temp.newFile("mapping.txt").toPath().toAbsolutePath();
+  }
+
+  private String invertedMapping() {
+     return StringUtils.lines(
+        "A -> " + LibraryClass.class.getTypeName() + ":",
+        "  void a() -> foo",
+        "B -> " + AnotherLibraryClass.class.getTypeName() + ":",
+        "  void a() -> foo"
+    );
+  }
+
+  // Note that all the test mappings below still need identity mappings for classes/memebers that
+  // are not renamed, for some reasons:
+  // 1) to mimic how R8 generates the mapping, where identity mappings are used in the same way.
+  // 2) otherwise, those classes/members will be renamed if minification is enabled, resulting in
+  //   no name clash, which is definitely not intended.
+
+  private String mappingToExistingClassName() {
+    return StringUtils.lines(
+        LibraryClass.class.getTypeName()
+            + " -> " + AnotherLibraryClass.class.getTypeName() + ":",
+        AnotherLibraryClass.class.getTypeName()
+            + " -> " + AnotherLibraryClass.class.getTypeName() + ":"
+    );
+  }
+
+  private String mappingToTheSameClassName() {
+    return StringUtils.lines(
+        LibraryClass.class.getTypeName() + " -> Clash:",
+        AnotherLibraryClass.class.getTypeName() + " -> Clash:"
+    );
+  }
+
+  private String mappingToExistingMethodName() {
+    return StringUtils.lines(
+        LibraryClass.class.getTypeName() + " -> A:",
+        "  void foo() -> bar",
+        AnotherLibraryClass.class.getTypeName() + " -> B:",
+        ProgramClass.class.getTypeName() + " -> " + ProgramClass.class.getTypeName() + ":",
+        "  void bar() -> bar"
+    );
+  }
+
+  private String mappingToTheSameMethodName() {
+    return StringUtils.lines(
+        LibraryClass.class.getTypeName() + " -> A:",
+        "  void foo() -> clash",
+        AnotherLibraryClass.class.getTypeName() + " -> B:",
+        "  void foo() -> clash",
+        ProgramClass.class.getTypeName() + " -> " + ProgramClass.class.getTypeName() + ":",
+        "  void bar() -> clash"
+    );
+  }
+
+  private void testProguard_inputJar(Path mappingFile) throws Exception {
+    testForProguard()
+        .addProgramFiles(libJar)
+        .addProgramFiles(prgJarThatUsesOriginalLib)
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-applymapping " + mappingFile)
+        .noTreeShaking()
+        .compile()
+        .run(MAIN)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private void testR8_inputJar(Path mappingFile) throws Exception {
+    testForR8(Backend.DEX)
+        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+        .addProgramFiles(libJar)
+        .addProgramFiles(prgJarThatUsesOriginalLib)
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-applymapping " + mappingFile)
+        .noTreeShaking()
+        .compile()
+        .run(MAIN)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private void testProguard_originalLibraryJar(Path mappingFile) throws Exception {
+    testForProguard()
+        .addLibraryFiles(libJar)
+        .addProgramFiles(prgJarThatUsesOriginalLib)
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-applymapping " + mappingFile)
+        .noTreeShaking()
+        .compile()
+        .run(MAIN)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private void testR8_originalLibraryJar(Path mappingFile) throws Exception {
+    testForR8(Backend.DEX)
+        .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), libJar)
+        .addProgramFiles(prgJarThatUsesOriginalLib)
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-applymapping " + mappingFile)
+        .noTreeShaking()
+        .compile()
+        .run(MAIN)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private void testProguard_minifiedLibraryJar(Path mappingFile) throws Exception {
+    testForProguard()
+        .addLibraryFiles(ToolHelper.getJava8RuntimeJar(), libJar)
+        .addProgramFiles(prgJarThatUsesMinifiedLib)
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-applymapping " + mappingFile)
+        .noTreeShaking()
+        .compile()
+        .run(MAIN)
+        .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, mappingToExistingClassName());
+    try {
+      testProguard_inputJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("Duplicate jar entry"));
+      assertThat(e.getMessage(), containsString("AnotherLibraryClass.class"));
+    }
+  }
+
+  @Test
+  public void testR8_prgClassRenamedToExistingPrgClass() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+    try {
+      testR8_inputJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getCause().getMessage(), containsString("Program type already present"));
+      assertThat(e.getCause().getMessage(), containsString("AnotherLibraryClass"));
+    }
+  }
+
+  @Test
+  public void testProguard_originalLibClassRenamedToExistingLibClass() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+    try {
+      testProguard_originalLibraryJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("can't find referenced method"));
+      assertThat(e.getMessage(), containsString("ProgramClass"));
+    }
+  }
+
+  @Ignore("b/123092153")
+  @Test
+  public void testR8_originalLibClassRenamedToExistingLibClass() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+    testR8_originalLibraryJar(mappingFile);
+  }
+
+  @Test
+  public void testProguard_prgClassesRenamedToSameName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+    try {
+      testProguard_inputJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("Duplicate jar entry [Clash.class]"));
+    }
+  }
+
+  @Test
+  public void testR8_prgClassesRenamedToSameName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+    try {
+      testR8_inputJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getCause().getMessage(), containsString("Program type already present"));
+      assertThat(e.getCause().getMessage(), containsString("Clash"));
+    }
+  }
+
+  @Test
+  public void testProguard_originalLibClassesRenamedToSameName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+    try {
+      testProguard_originalLibraryJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("can't find referenced method"));
+      assertThat(e.getMessage(), containsString("ProgramClass"));
+    }
+  }
+
+  @Ignore("b/123092153")
+  @Test
+  public void testR8_originalLibClassesRenamedToSameName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+    testR8_originalLibraryJar(mappingFile);
+  }
+
+  @Test
+  public void testProguard_prgMethodRenamedToExistingName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+    try {
+      testProguard_inputJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'bar'"));
+      assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+      assertThat(e.getMessage(), containsString("which is already being mapped to 'bar'"));
+    }
+  }
+
+  @Ignore("b/123092153")
+  @Test
+  public void testR8_prgMethodRenamedToExistingName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+    try {
+      testR8_inputJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'bar'"));
+      assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+      assertThat(e.getMessage(), containsString("which is already being mapped to 'bar'"));
+    }
+  }
+
+  @Test
+  public void testProguard_originalLibMethodRenamedToExistingName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+    try {
+      testProguard_originalLibraryJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("can't find referenced method"));
+      assertThat(e.getMessage(), containsString("ProgramClass"));
+    }
+  }
+
+  @Ignore("b/123092153")
+  @Test
+  public void testR8_originalLibMethodRenamedToExistingName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+    testR8_originalLibraryJar(mappingFile);
+  }
+
+  @Test
+  public void testProguard_prgMethodRenamedToSameName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+    try {
+      testProguard_inputJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'clash'"));
+      assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+      assertThat(e.getMessage(), containsString("which is already being mapped to 'clash'"));
+    }
+  }
+
+  @Ignore("b/123092153")
+  @Test
+  public void testR8_prgMethodRenamedToSameName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+    try {
+      testR8_inputJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'bar'"));
+      assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+      assertThat(e.getMessage(), containsString("which is already being mapped to 'bar'"));
+    }
+  }
+
+  @Test
+  public void testProguard_originalLibMethodRenamedToSameName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+    try {
+      testProguard_originalLibraryJar(mappingFile);
+      fail("Expect compilation failure.");
+    } catch (CompilationFailedException e) {
+      assertThat(e.getMessage(), containsString("can't find referenced method"));
+      assertThat(e.getMessage(), containsString("ProgramClass"));
+    }
+  }
+
+  @Ignore("b/123092153")
+  @Test
+  public void testR8_originalLibMethodRenamedToSameName() throws Exception {
+    FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+    testR8_originalLibraryJar(mappingFile);
+  }
+
+  @Test
+  public void testProguard_minifiedLib() throws Exception {
+    FileUtils.writeTextFile(mappingFile, invertedMapping());
+    try {
+      testProguard_minifiedLibraryJar(mappingFile);
+    } catch (CompilationFailedException e) {
+      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/NameClashTestDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTestDump.java
new file mode 100644
index 0000000..7da3800
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTestDump.java
@@ -0,0 +1,131 @@
+// 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 org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// asmified the following:
+// class ProgramClass extends LibraryClass {
+//   static String PRG_MSG = "ProgramClass::bar";
+//   void bar() {
+//     System.out.println(PRG_MSG);
+//   }
+//   public static void main(String[] args) {
+//     new AnotherLibraryClass().foo();
+//     ProgramClass instance = new ProgramClass();
+//     instance.foo();
+//     instance.bar();
+//   }
+// }
+//
+// then replaced the use of LibraryClass and AnotherLibraryClass with A and B so that providing
+// LibraryClass and AnotherLibraryClass, along with mapping, as a minified library:
+//   A -> LibraryClass:
+//     void a() -> foo
+//   B -> AnotherLibraryClass:
+//     void a() -> foo
+class ProgramClassDump implements Opcodes {
+
+  public static byte[] dump () throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    String pkg = LibraryClass.class.getPackage().getName();
+
+    classWriter.visit(V1_8, ACC_SUPER, pkg.replace('.', '/') + "/ProgramClass", null, "A", null);
+
+    classWriter.visitSource("Test.java", null);
+
+    {
+      fieldVisitor = classWriter.visitField(
+          ACC_STATIC, "PRG_MSG", "Ljava/lang/String;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(15, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "bar", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(18, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitFieldInsn(GETSTATIC, "ProgramClass", "PRG_MSG", "Ljava/lang/String;");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(19, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(
+          ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(22, label0);
+      methodVisitor.visitTypeInsn(NEW, "B");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "B", "<init>", "()V", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "B", "a", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(23, label1);
+      methodVisitor.visitTypeInsn(NEW, "ProgramClass");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "ProgramClass", "<init>", "()V", false);
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(24, label2);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "ProgramClass", "a", "()V", false);
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(25, label3);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "ProgramClass", "bar", "()V", false);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(26, label4);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(16, label0);
+      methodVisitor.visitLdcInsn("ProgramClass::bar");
+      methodVisitor.visitFieldInsn(PUTSTATIC, "ProgramClass", "PRG_MSG", "Ljava/lang/String;");
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
new file mode 100644
index 0000000..25497e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
@@ -0,0 +1,138 @@
+// 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.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ProguardTestRunResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class ToBeRenamedClass {
+  void toBeRenamedMethod() {
+    System.out.println("foo");
+  }
+}
+
+class WhenToApplyTestRunner {
+  public static void main(String[] args) {
+    ToBeRenamedClass instance = new ToBeRenamedClass();
+    instance.toBeRenamedMethod();
+    System.out.println(instance.getClass().getSimpleName());
+  }
+}
+
+@RunWith(Parameterized.class)
+public class WhenToApplyTest extends TestBase {
+
+  @ClassRule
+  public static TemporaryFolder temporaryFolder = ToolHelper.getTemporaryFolderForTest();
+
+  private static Class<?> MAIN = WhenToApplyTestRunner.class;
+  private static String RENAMED_CLASS_NAME =
+      ToBeRenamedClass.class.getPackage().getName() + ".ABC";
+  private static String NORMAL_OUTPUT = StringUtils.lines("foo", "ToBeRenamedClass");
+  private static String APPLIED_OUTPUT = StringUtils.lines("foo", "ABC");
+  private static String RENAMED_OUTPUT = StringUtils.lines("foo", "a");
+
+  private static Path mappingFile;
+  private static Path configuration;
+  private boolean minification;
+
+  @Parameterized.Parameters(name = "minification: {0}")
+  public static Boolean[] data() {
+    return BooleanUtils.values();
+  }
+
+  public WhenToApplyTest(boolean minification) {
+    this.minification = minification;
+  }
+
+  @BeforeClass
+  public static void setUpMappingFile() throws Exception {
+    mappingFile = temporaryFolder.newFile("mapping.txt").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(mappingFile, StringUtils.lines(
+        ToBeRenamedClass.class.getTypeName() + " -> " + RENAMED_CLASS_NAME + ":",
+        "  void toBeRenamedMethod() -> abc"));
+    configuration = temporaryFolder.newFile("pg.conf").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(configuration, StringUtils.lines(
+        "-dontoptimize",
+        "-applymapping " + mappingFile
+    ));
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    ProguardTestRunResult result = testForProguard()
+        .addProgramClasses(ToBeRenamedClass.class, MAIN)
+        .addKeepMainRule(MAIN)
+        .addKeepRuleFiles(configuration)
+        .minification(minification)
+        .compile().run(MAIN);
+    if (minification) {
+      result.assertSuccessWithOutput(APPLIED_OUTPUT);
+    } else {
+      result.assertSuccessWithOutput(NORMAL_OUTPUT);
+    }
+    result.inspect(inspector -> {
+      ClassSubject classSubject = inspector.clazz(ToBeRenamedClass.class);
+      assertThat(classSubject, isPresent());
+      // As renaming won't happen again, we can use the original name to search for the method.
+      MethodSubject methodSubject = classSubject.uniqueMethodWithName("toBeRenamedMethod");
+      assertThat(methodSubject, isPresent());
+      String methodName =
+          minification
+              ? "abc"                // mapped name with minification
+              : "toBeRenamedMethod"; // original name without minification
+      assertEquals(methodName, methodSubject.getFinalName());
+    });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult result = testForR8(Backend.DEX)
+        .addProgramClasses(ToBeRenamedClass.class, MAIN)
+        .addKeepMainRule(MAIN)
+        .addKeepRuleFiles(configuration)
+        .minification(minification)
+        .compile().run(MAIN);
+    if (minification) {
+      result.assertSuccessWithOutput(RENAMED_OUTPUT);
+    } else {
+      result.assertSuccessWithOutput(APPLIED_OUTPUT);
+    }
+    result.inspect(inspector -> {
+      ClassSubject classSubject = inspector.clazz(RENAMED_CLASS_NAME);
+      assertThat(classSubject, isPresent());
+      // Mapped name will be regarded as an original name if minification is disabled.
+      String methodName =
+          minification
+              ? "toBeRenamedMethod" // original name
+              : "abc";              // mapped name without minification
+      MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName);
+      assertThat(methodSubject, isPresent());
+      methodName =
+          minification
+              ? "a"    // minified name
+              : "abc"; // mapped name without minification
+      assertEquals(methodName, methodSubject.getFinalName());
+    });
+  }
+
+}
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index fa46485..be380ed 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -74,7 +74,16 @@
 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]
+  zipalign_path = (
+      'zipalign' if 'build_tools' in os.environ.get('PATH')
+      else os.path.join(utils.ANDROID_BUILD_TOOLS, 'zipalign'))
+  cmd = [
+    zipalign_path,
+    '-f',
+    '4',
+    signed_apk,
+    aligned_apk
+  ]
   utils.RunCmd(cmd, quiet=quiet)
   return signed_apk
 
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
index ad26959..9f2f7e4 100755
--- a/tools/run_bootstrap_benchmark.py
+++ b/tools/run_bootstrap_benchmark.py
@@ -60,11 +60,11 @@
       sys.exit(return_code)
 
     dex(r8_output, d8_r8_output)
-    print "BootstrapR8(CodeSize):", os.path.getsize(r8_output)
-    print "BootstrapR8Dex(CodeSize):", os.path.getsize(d8_r8_output)
+    print "BootstrapR8(CodeSize):", utils.uncompressed_size(r8_output)
+    print "BootstrapR8Dex(CodeSize):", utils.uncompressed_size(d8_r8_output)
 
     dex(PINNED_PGR8_JAR, d8_pg_output)
-    print "BootstrapR8PG(CodeSize):", os.path.getsize(PINNED_PGR8_JAR)
-    print "BootstrapR8PGDex(CodeSize):", os.path.getsize(d8_pg_output)
+    print "BootstrapR8PG(CodeSize):", utils.uncompressed_size(PINNED_PGR8_JAR)
+    print "BootstrapR8PGDex(CodeSize):", utils.uncompressed_size(d8_pg_output)
 
   sys.exit(0)
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 2454863..aacfbe9 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -75,6 +75,11 @@
       'app_id': 'org.schabi.newpipe',
       'git_repo': 'https://github.com/christofferqa/NewPipe',
   },
+  'rover-android': {
+    'app_id': 'io.rover.app.debug',
+    'app_module': 'debug-app',
+    'git_repo': 'https://github.com/mkj-gram/rover-android.git',
+  },
   'Signal-Android': {
     'app_id': 'org.thoughtcrime.securesms',
     'app_module': '',
@@ -117,13 +122,6 @@
   },
 }
 
-# Common environment setup.
-user_home = os.path.expanduser('~')
-android_home = os.path.join(user_home, 'Android', 'Sdk')
-android_build_tools_version = '28.0.3'
-android_build_tools = os.path.join(
-    android_home, 'build-tools', android_build_tools_version)
-
 # TODO(christofferqa): Do not rely on 'emulator-5554' name
 emulator_id = 'emulator-5554'
 
@@ -375,7 +373,7 @@
       app, config, checkout_dir, proguard_config_dest)
 
   env = {}
-  env['ANDROID_HOME'] = android_home
+  env['ANDROID_HOME'] = utils.ANDROID_HOME
   env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
 
   releaseTarget = config.get('releaseTarget')
@@ -420,7 +418,7 @@
       keystore = 'app.keystore'
       keystore_password = 'android'
       apk_utils.sign_with_apksigner(
-          android_build_tools,
+          utils.ANDROID_BUILD_TOOLS,
           unsigned_apk,
           signed_apk,
           keystore,
diff --git a/tools/utils.py b/tools/utils.py
index b39a168..ad85089 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -12,6 +12,7 @@
 import sys
 import tarfile
 import tempfile
+import zipfile
 
 ANDROID_JAR_DIR = 'third_party/android_jar/lib-v{api}'
 ANDROID_JAR = os.path.join(ANDROID_JAR_DIR, 'android.jar')
@@ -54,6 +55,13 @@
 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')
 
+# Common environment setup.
+USER_HOME = os.path.expanduser('~')
+ANDROID_HOME = os.path.join(USER_HOME, 'Android', 'Sdk')
+ANDROID_BUILD_TOOLS_VERSION = '28.0.3'
+ANDROID_BUILD_TOOLS = os.path.join(
+    ANDROID_HOME, 'build-tools', ANDROID_BUILD_TOOLS_VERSION)
+
 def Print(s, quiet=False):
   if quiet:
     return
@@ -405,3 +413,9 @@
 
 def is_bot():
   return 'BUILDBOT_BUILDERNAME' in os.environ
+
+
+def uncompressed_size(path):
+  return sum(z.file_size for z in zipfile.ZipFile(path).infolist())
+
+