Add a new dex version and api level

- Refactor around dex version to have an enumeration similar
to AndroidApiLevel

Change-Id: I9c2d09dd7cf0d51055959041e10c9cdb627e93e7
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 6169178..cc75fd0 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
-import static com.android.tools.r8.dex.Constants.ANDROID_N_DEX_VERSION;
-import static com.android.tools.r8.dex.Constants.ANDROID_O_DEX_VERSION;
 import static com.android.tools.r8.graph.ClassKind.CLASSPATH;
 import static com.android.tools.r8.graph.ClassKind.LIBRARY;
 import static com.android.tools.r8.graph.ClassKind.PROGRAM;
@@ -26,6 +24,7 @@
 import com.android.tools.r8.naming.ProguardMapReader;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.ClassProvider;
 import com.android.tools.r8.utils.ClasspathClassCollection;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -95,38 +94,18 @@
   }
 
   private int verifyOrComputeMinApiLevel(int computedMinApiLevel, DexFile file) {
-    int version = file.getDexVersion();
+    DexVersion version = DexVersion.getDexVersion(file.getDexVersion());
     if (options.minApiLevel == AndroidApiLevel.getDefault().getLevel()) {
-      computedMinApiLevel = Math.max(computedMinApiLevel, dexVersionToMinSdk(version));
-    } else if (!minApiMatchesDexVersion(version)) {
-      throw new CompilationError("Dex file with version '" + version +
+      computedMinApiLevel = Math
+          .max(computedMinApiLevel, AndroidApiLevel.getMinAndroidApiLevel(version).getLevel());
+    } else if (!version
+        .matchesApiLevel(AndroidApiLevel.getAndroidApiLevel(options.minApiLevel))) {
+      throw new CompilationError("Dex file with version '" + version.getIntValue() +
           "' cannot be used with min sdk level '" + options.minApiLevel + "'.");
     }
     return computedMinApiLevel;
   }
 
-  private boolean minApiMatchesDexVersion(int version) {
-    switch (version) {
-      case ANDROID_O_DEX_VERSION:
-        return options.minApiLevel >= AndroidApiLevel.O.getLevel();
-      case ANDROID_N_DEX_VERSION:
-        return options.minApiLevel >= AndroidApiLevel.N.getLevel();
-      default:
-        return true;
-    }
-  }
-
-  private int dexVersionToMinSdk(int version) {
-    switch (version) {
-      case ANDROID_O_DEX_VERSION:
-        return AndroidApiLevel.O.getLevel();
-      case ANDROID_N_DEX_VERSION:
-        return AndroidApiLevel.N.getLevel();
-      default:
-        return AndroidApiLevel.getDefault().getLevel();
-    }
-  }
-
   private void readProguardMap(DexApplication.Builder builder, ExecutorService executorService,
       List<Future<?>> futures) {
     // Read the Proguard mapping file in parallel with DexCode and DexProgramClass items.
diff --git a/src/main/java/com/android/tools/r8/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index 58ff5d7..3a06f1d 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -11,16 +11,6 @@
   public static final byte[] VDEX_FILE_MAGIC_PREFIX = {'v', 'd', 'e', 'x'};
   public static final byte[] VDEX_FILE_VERSION = {'0', '1', '0', '\0'};
 
-  /** dex file version number for Android O (API level 26) */
-  public static final int ANDROID_O_DEX_VERSION = 38;
-  public static final byte[] ANDROID_O_DEX_VERSION_BYTES = {'0', '3', '8'};
-  /** dex file version number for Android N (API level 24) */
-  public static final int ANDROID_N_DEX_VERSION = 37;
-  public static final byte[] ANDROID_N_DEX_VERSION_BYTES = {'0', '3', '7'};
-  /** dex file version number for all releases prior to Android N */
-  public static final int ANDROID_PRE_N_DEX_VERSION = 35;
-  public static final byte[] ANDROID_PRE_N_DEX_VERSION_BYTES = {'0', '3', '5'};
-
   /** vdex file version number for Android O (API level 26) */
   public static final int ANDROID_O_VDEX_VERSION = 10;
 
diff --git a/src/main/java/com/android/tools/r8/dex/DexFile.java b/src/main/java/com/android/tools/r8/dex/DexFile.java
index 2865300..d2db9d6 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFile.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFile.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.dex.Constants.DEX_FILE_MAGIC_PREFIX;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.DexVersion;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
@@ -54,14 +55,17 @@
     byte versionByte = buffer.get(index++);
     int version;
     switch (versionByte) {
+      case '9':
+        version = DexVersion.V39.getIntValue();
+        break;
       case '8':
-        version = Constants.ANDROID_O_DEX_VERSION;
+        version =  DexVersion.V38.getIntValue();
         break;
       case '7':
-        version = Constants.ANDROID_N_DEX_VERSION;
+        version =  DexVersion.V37.getIntValue();
         break;
       case '5':
-        version = Constants.ANDROID_PRE_N_DEX_VERSION;
+        version =  DexVersion.V35.getIntValue();
         break;
       default:
         throw new CompilationError("Dex file has invalid version number: " + name);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index afa7105..70fd655 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LebUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -733,20 +734,13 @@
     dest.forward(size * Constants.TYPE_MAP_LIST_ITEM_SIZE);
   }
 
-  private static byte[] convertApiLevelToDexVersion(int apiLevel) {
-    if (apiLevel >= AndroidApiLevel.O.getLevel()) {
-      return Constants.ANDROID_O_DEX_VERSION_BYTES;
-    }
-    if (apiLevel >= AndroidApiLevel.N.getLevel()) {
-      return Constants.ANDROID_N_DEX_VERSION_BYTES;
-    }
-    return Constants.ANDROID_PRE_N_DEX_VERSION_BYTES;
-  }
-
   private void writeHeader(Layout layout) {
     dest.moveTo(0);
     dest.putBytes(Constants.DEX_FILE_MAGIC_PREFIX);
-    dest.putBytes(convertApiLevelToDexVersion(options.minApiLevel));
+    dest.putBytes(
+        DexVersion
+            .getDexVersion(AndroidApiLevel.getAndroidApiLevel(options.minApiLevel))
+            .getBytes());
     dest.putByte(Constants.DEX_FILE_MAGIC_SUFFIX);
     // Leave out checksum and signature for now.
     dest.moveTo(Constants.FILE_SIZE_OFFSET);
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 04ab202..b1bdfde 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -3,10 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.errors.Unreachable;
+
 /**
  * Android API level description
  */
 public enum AndroidApiLevel {
+  P(27),
   O(26),
   N_MR1(25),
   N(24),
@@ -51,4 +54,84 @@
   public static AndroidApiLevel getDefault() {
     return AndroidApiLevel.B;
   }
+
+  public DexVersion getDexVersion() {
+    return DexVersion.getDexVersion(this);
+  }
+
+  public static AndroidApiLevel getMinAndroidApiLevel(DexVersion dexVersion) {
+    switch (dexVersion) {
+      case V35:
+        return AndroidApiLevel.B;
+      case V37:
+        return AndroidApiLevel.N;
+      case V38:
+        return AndroidApiLevel.O;
+      case V39:
+        return AndroidApiLevel.P;
+      default:
+        throw new Unreachable();
+    }
+  }
+
+  public static AndroidApiLevel getAndroidApiLevel(int apiLevel) {
+    switch (apiLevel) {
+      case 1:
+        return B;
+      case 2:
+        return B_1_1;
+      case 3:
+        return C;
+      case 4:
+        return D;
+      case 5:
+        return E;
+      case 6:
+        return E_0_1;
+      case 7:
+        return E_MR1;
+      case 8:
+        return F;
+      case 9:
+        return G;
+      case 10:
+        return G_MR1;
+      case 11:
+        return H;
+      case 12:
+        return H_MR1;
+      case 13:
+        return H_MR2;
+      case 14:
+        return I;
+      case 15:
+        return I_MR1;
+      case 16:
+        return J;
+      case 17:
+        return J_MR1;
+      case 18:
+        return J_MR2;
+      case 19:
+        return K;
+      case 20:
+        return K_WATCH;
+      case 21:
+        return L;
+      case 22:
+        return L_MR1;
+      case 23:
+        return M;
+      case 24:
+        return N;
+      case 25:
+        return N_MR1;
+      case 26:
+        return O;
+      case 27:
+        return P;
+      default:
+        throw new Unreachable();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
new file mode 100644
index 0000000..e45a0dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.errors.Unreachable;
+
+/**
+ * Android dex version
+ */
+public enum DexVersion {
+  V35(35, new byte[]{'0', '3', '5'}),
+  V37(37, new byte[]{'0', '3', '7'}),
+  V38(38, new byte[]{'0', '3', '8'}),
+  V39(39, new byte[]{'0', '3', '9'});
+
+  private final int dexVersion;
+
+  private final byte[] dexVersionBytes;
+
+  DexVersion(int dexVersion, byte[] dexVersionBytes) {
+    this.dexVersion = dexVersion;
+    this.dexVersionBytes = dexVersionBytes;
+  }
+
+  public int getIntValue() {
+    return dexVersion;
+  }
+
+  public byte[] getBytes() {
+    return dexVersionBytes;
+  }
+
+  public boolean matchesApiLevel(AndroidApiLevel androidApiLevel) {
+    switch (this) {
+      case V35:
+        return true;
+      case V37:
+        return androidApiLevel.getLevel() >= AndroidApiLevel.N.getLevel();
+      case V38:
+        return androidApiLevel.getLevel() >= AndroidApiLevel.O.getLevel();
+      case V39:
+        return androidApiLevel.getLevel() >= AndroidApiLevel.P.getLevel();
+      default:
+        throw new Unreachable();
+    }
+  }
+
+  public static DexVersion getDexVersion(AndroidApiLevel androidApiLevel) {
+    switch (androidApiLevel) {
+      case P:
+        return DexVersion.V39;
+      case O:
+        return DexVersion.V38;
+      case N_MR1:
+      case N:
+        return DexVersion.V37;
+      case B:
+      case B_1_1:
+      case C:
+      case D:
+      case E:
+      case E_0_1:
+      case E_MR1:
+      case F:
+      case G:
+      case G_MR1:
+      case H:
+      case H_MR1:
+      case H_MR2:
+      case I:
+      case I_MR1:
+      case J:
+      case J_MR1:
+      case J_MR2:
+      case K:
+      case K_WATCH:
+      case L:
+      case L_MR1:
+      case M:
+        return DexVersion.V35;
+      default :
+        throw new Unreachable();
+    }
+  }
+
+  public static DexVersion getDexVersion(int intValue) {
+    switch (intValue) {
+      case 35:
+        return V35;
+      case 37:
+        return V37;
+      case 38:
+        return V38;
+      case 39:
+        return V39;
+      default:
+        throw new Unreachable();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/EncodedFloatingValueTest.java b/src/test/java/com/android/tools/r8/dex/EncodedFloatingValueTest.java
index 3a4ea04..2bcce43 100644
--- a/src/test/java/com/android/tools/r8/dex/EncodedFloatingValueTest.java
+++ b/src/test/java/com/android/tools/r8/dex/EncodedFloatingValueTest.java
@@ -7,6 +7,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.EncodedValueUtils;
 import java.util.Arrays;
 import java.util.Collection;
@@ -45,7 +47,7 @@
   private DexFile createDexFileWithContent(byte[] bytes) {
     DexOutputBuffer buffer = new DexOutputBuffer();
     buffer.putBytes(Constants.DEX_FILE_MAGIC_PREFIX);
-    buffer.putBytes(Constants.ANDROID_PRE_N_DEX_VERSION_BYTES);
+    buffer.putBytes(AndroidApiLevel.B.getDexVersion().getBytes());
     buffer.putByte(Constants.DEX_FILE_MAGIC_SUFFIX);
     buffer.putBytes(bytes);
     DexFile dexFile = new DexFile(buffer.asArray());
diff --git a/src/test/java/com/android/tools/r8/dex/Leb128Test.java b/src/test/java/com/android/tools/r8/dex/Leb128Test.java
index 2b6ab9e..4cfefa1 100644
--- a/src/test/java/com/android/tools/r8/dex/Leb128Test.java
+++ b/src/test/java/com/android/tools/r8/dex/Leb128Test.java
@@ -5,6 +5,8 @@
 
 import static com.android.tools.r8.dex.Constants.DEX_MAGIC_SIZE;
 
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.LebUtils;
 import java.util.Arrays;
 import java.util.Collection;
@@ -48,7 +50,7 @@
   private DexFile createDexFileWithContent(byte[] bytes) {
     DexOutputBuffer buffer = new DexOutputBuffer();
     buffer.putBytes(Constants.DEX_FILE_MAGIC_PREFIX);
-    buffer.putBytes(Constants.ANDROID_PRE_N_DEX_VERSION_BYTES);
+    buffer.putBytes(AndroidApiLevel.B.getDexVersion().getBytes());
     buffer.putByte(Constants.DEX_FILE_MAGIC_SUFFIX);
     buffer.putBytes(bytes);
     DexFile dexFile = new DexFile(buffer.asArray());