Modify the method used for a ZipEntry when changing compression level.

When writing a zip entry that's stored, we must pre-calculate the sizes and CRC in order
for Other Zip Consuming Tools to be happy.

Test Plan:
`buck test --all`
Build prod apps and verify that the `RepackZipStep` was actually effective.
diff --git a/src/com/facebook/buck/zip/CustomZipEntry.java b/src/com/facebook/buck/zip/CustomZipEntry.java
index cf324dc..062badb 100644
--- a/src/com/facebook/buck/zip/CustomZipEntry.java
+++ b/src/com/facebook/buck/zip/CustomZipEntry.java
@@ -30,16 +30,32 @@
 
   public CustomZipEntry(ZipEntry other) {
     super(other);
+    setDefaultMethodIfMethodUnset();
   }
 
   public CustomZipEntry(String name) {
     super(name);
+    setDefaultMethodIfMethodUnset();
+  }
+
+  private void setDefaultMethodIfMethodUnset() {
+    if (getMethod() == -1) {
+      setMethod(DEFLATED);
+    }
   }
 
   public void setCompressionLevel(int compressionLevel) {
     Preconditions.checkArgument(
         compressionLevel >= NO_COMPRESSION && compressionLevel <= BEST_COMPRESSION);
     this.compressionLevel = compressionLevel;
+
+    // We need to update the underlying method declared
+    setMethod(compressionLevel == NO_COMPRESSION ? STORED : DEFLATED);
+
+    // Reset the various fields that need to be updated.
+    setCrc(0);
+    setSize(0);
+    setCompressedSize(0);
   }
 
   public int getCompressionLevel() {
diff --git a/src/com/facebook/buck/zip/EntryAccounting.java b/src/com/facebook/buck/zip/EntryAccounting.java
index 16ddd5b..81bcc53 100644
--- a/src/com/facebook/buck/zip/EntryAccounting.java
+++ b/src/com/facebook/buck/zip/EntryAccounting.java
@@ -76,10 +76,6 @@
       entry.setTime(clock.currentTimeMillis());
     }
 
-    if (method == Method.DEFLATE) {
-      flags |= DATA_DESCRIPTOR_FLAG;
-    }
-
     if (entry instanceof CustomZipEntry) {
       deflater.setLevel(((CustomZipEntry) entry).getCompressionLevel());
     }
@@ -160,6 +156,35 @@
   }
 
   public long writeLocalFileHeader(OutputStream out) throws IOException {
+    if (method == Method.DEFLATE) {
+      flags |= DATA_DESCRIPTOR_FLAG;
+
+      // See http://www.pkware.com/documents/casestudies/APPNOTE.TXT (section 4.4.4)
+      // Essentially, we're about to set bits 1 and 2 to indicate to tools such as zipinfo which
+      // level of compression we're using. If we've not set a compression level, then we're using
+      // the default one, which is right. It turns out. For your viewing pleasure:
+      //
+      // +----------+-------+-------+
+      // | Level    | Bit 1 | Bit 2 |
+      // +----------+-------+-------+
+      // | Fastest  |   0   |   1   |
+      // | Normal   |   0   |   0   |
+      // | Best     |   1   |   0   |
+      // +----------+-------+-------+
+      if (entry instanceof CustomZipEntry) {
+        int level = ((CustomZipEntry) entry).getCompressionLevel();
+        switch (level) {
+          case Deflater.BEST_COMPRESSION:
+            flags |= (1 << 1);
+            break;
+
+          case Deflater.BEST_SPEED:
+            flags |= (1 << 2);
+            break;
+        }
+      }
+    }
+
     try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
       ByteIo.writeInt(stream, ZipEntry.LOCSIG);
 
diff --git a/src/com/facebook/buck/zip/RepackZipEntriesStep.java b/src/com/facebook/buck/zip/RepackZipEntriesStep.java
index eb9850d..06e9bc6 100644
--- a/src/com/facebook/buck/zip/RepackZipEntriesStep.java
+++ b/src/com/facebook/buck/zip/RepackZipEntriesStep.java
@@ -21,12 +21,16 @@
 import com.facebook.buck.step.Step;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.hash.Hashing;
 import com.google.common.io.ByteStreams;
 
 import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.logging.Level;
@@ -89,8 +93,24 @@
         if (entries.contains(customEntry.getName())) {
           customEntry.setCompressionLevel(compressionLevel);
         }
+
+        InputStream toUse;
+        // If we're using STORED files, we must pre-calculate the CRC.
+        if (customEntry.getMethod() == ZipEntry.STORED) {
+          try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            ByteStreams.copy(in, bos);
+            byte[] bytes = bos.toByteArray();
+            customEntry.setCrc(Hashing.crc32().hashBytes(bytes).padToLong());
+            customEntry.setSize(bytes.length);
+            customEntry.setCompressedSize(bytes.length);
+            toUse = new ByteArrayInputStream(bytes);
+          }
+        } else {
+          toUse = in;
+        }
+
         out.putNextEntry(customEntry);
-        ByteStreams.copy(in, out);
+        ByteStreams.copy(toUse, out);
         out.closeEntry();
       }
 
diff --git a/test/com/facebook/buck/zip/ZipOutputStreamTest.java b/test/com/facebook/buck/zip/ZipOutputStreamTest.java
index 06453d1..aaad0a3 100644
--- a/test/com/facebook/buck/zip/ZipOutputStreamTest.java
+++ b/test/com/facebook/buck/zip/ZipOutputStreamTest.java
@@ -49,6 +49,7 @@
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
+import java.util.zip.Deflater;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 import java.util.zip.ZipInputStream;
@@ -343,8 +344,12 @@
 
       entry = new CustomZipEntry("stored");
       entry.setCompressionLevel(NO_COMPRESSION);
+      byte[] bytes = "stored".getBytes();
+      entry.setSize(bytes.length);
+      entry.setCrc(Hashing.crc32().hashBytes(bytes).padToLong());
       out.putNextEntry(entry);
-      out.write("stored".getBytes());
+
+      out.write(bytes);
 
       entry = new CustomZipEntry("best");
       entry.setCompressionLevel(BEST_COMPRESSION);
@@ -360,13 +365,28 @@
       assertNotEquals(entry.getCompressedSize(), entry.getSize());
 
       entry = in.getNextEntry();
+      ByteStreams.copy(in, ByteStreams.nullOutputStream());
       assertEquals("stored", entry.getName());
       assertEquals(entry.getCompressedSize(), entry.getSize());
 
       entry = in.getNextEntry();
+      ByteStreams.copy(in, ByteStreams.nullOutputStream());
       assertEquals("best", entry.getName());
       ByteStreams.copy(in, ByteStreams.nullOutputStream());
       assertThat(entry.getCompressedSize(), lessThan(defaultCompressedSize));
     }
   }
+
+  @Test
+  public void shouldChangeMethodWhenCompressionLevelIsChanged() {
+    CustomZipEntry entry = new CustomZipEntry("cake");
+    assertEquals(ZipEntry.DEFLATED, entry.getMethod());
+    assertEquals(Deflater.DEFAULT_COMPRESSION, entry.getCompressionLevel());
+
+    entry.setCompressionLevel(NO_COMPRESSION);
+    assertEquals(ZipEntry.STORED, entry.getMethod());
+
+    entry.setCompressionLevel(BEST_COMPRESSION);
+    assertEquals(ZipEntry.DEFLATED, entry.getMethod());
+  }
 }