Adding platform to the build (#158)

diff --git a/Android.mk b/Android.mk
index b682e94..ca32c8f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -72,15 +72,14 @@
 	mkdir -p $(dir $@)
 	$< > $@
 
-common_java_files := $(filter-out \
-	%/org/conscrypt/Platform.java \
-	%/org/conscrypt/NativeCryptoJni.java \
-	, $(call all-java-files-under,common/src/main/java))
+common_java_files := $(call all-java-files-under,common/src/main/java)
+
+bundled_main_java_files := $(common_java_files)
+bundled_main_java_files += $(call all-java-files-under,platform/src/main/java)
 
 # Create the conscrypt library
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(common_java_files)
-LOCAL_SRC_FILES += $(call all-java-files-under,platform/src/main/java)
+LOCAL_SRC_FILES := $(bundled_main_java_files)
 LOCAL_GENERATED_SOURCES := $(conscrypt_gen_java_files)
 LOCAL_JAVA_LIBRARIES := core-oj core-libart
 LOCAL_NO_STANDARD_LIBRARIES := true
@@ -96,8 +95,7 @@
 # The build system may or may not strip the conscrypt jar, but this one will
 # not be stripped. See b/24535627.
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(common_java_files)
-LOCAL_SRC_FILES += $(call all-java-files-under,platform/src/main/java)
+LOCAL_SRC_FILES := $(bundled_main_java_files)
 LOCAL_GENERATED_SOURCES := $(conscrypt_gen_java_files)
 LOCAL_JAVA_LIBRARIES := core-oj core-libart
 LOCAL_NO_STANDARD_LIBRARIES := true
@@ -111,8 +109,7 @@
 
 # Create the conscrypt library without jarjar for tests
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(common_java_files)
-LOCAL_SRC_FILES += $(call all-java-files-under,platform/src/main/java)
+LOCAL_SRC_FILES := $(bundled_main_java_files)
 LOCAL_GENERATED_SOURCES := $(conscrypt_gen_java_files)
 LOCAL_JAVA_LIBRARIES := core-oj core-libart
 LOCAL_NO_STANDARD_LIBRARIES := true
@@ -218,8 +215,7 @@
 
 # Make the conscrypt-hostdex library
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(common_java_files)
-LOCAL_SRC_FILES += $(call all-java-files-under,platform/src/main/java)
+LOCAL_SRC_FILES := $(bundled_main_java_files)
 LOCAL_GENERATED_SOURCES := $(conscrypt_gen_java_files)
 LOCAL_JAVACFLAGS := $(local_javac_flags)
 LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
@@ -231,8 +227,7 @@
 
 # Make the conscrypt-hostdex-nojarjar for tests
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(common_java_files)
-LOCAL_SRC_FILES += $(call all-java-files-under,platform/src/main/java)
+LOCAL_SRC_FILES := $(bundled_main_java_files)
 LOCAL_GENERATED_SOURCES := $(conscrypt_gen_java_files)
 LOCAL_JAVACFLAGS := $(local_javac_flags)
 LOCAL_BUILD_HOST_DEX := true
diff --git a/android-stub/build.gradle b/android-stub/build.gradle
index d0af41a..e57cd88 100644
--- a/android-stub/build.gradle
+++ b/android-stub/build.gradle
@@ -4,6 +4,10 @@
 sourceCompatibility = androidMinJavaVersion
 targetCompatibility = androidMinJavaVersion
 
+dependencies {
+    compile project(':conscrypt-libcore-stub')
+}
+
 // Don't include this artifact in the distribution.
 tasks.install.enabled = false
 tasks.uploadArchives.enabled = false;
diff --git a/build.gradle b/build.gradle
index 45e4240..18e8b48 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,7 +17,7 @@
 }
 
 subprojects {
-    def androidProject = project.name == 'conscrypt-android'
+    def androidProject = (project.name == 'conscrypt-android') || (project.name == 'conscrypt-android-platform')
     if (!androidProject) {
         apply plugin: 'java'
         apply plugin: 'cpp'
@@ -82,15 +82,14 @@
 
         jmhVersion = '1.17.4'
         libraries = [
-                roboelectric: 'org.robolectric:android-all:5.0.0_r2-robolectric-1',
+                roboelectric: 'org.robolectric:android-all:7.1.0_r7-robolectric-0',
 
                 // Test dependencies.
-                guava : 'com.google.guava:guava:19.0',
                 junit  : 'junit:junit:4.12',
                 mockito: 'org.mockito:mockito-core:1.9.5',
                 truth  : 'com.google.truth:truth:0.28',
-                bouncycastle_provider: 'org.bouncycastle:bcprov-jdk15on:1.55',
-                bouncycastle_apis: 'org.bouncycastle:bcpkix-jdk15on:1.55',
+                bouncycastle_provider: 'org.bouncycastle:bcprov-jdk15on:1.56',
+                bouncycastle_apis: 'org.bouncycastle:bcpkix-jdk15on:1.56',
 
                 // Benchmark dependencies
                 jmh_core: "org.openjdk.jmh:jmh-core:${jmhVersion}",
diff --git a/libcore-stub/build.gradle b/libcore-stub/build.gradle
new file mode 100644
index 0000000..92e5344
--- /dev/null
+++ b/libcore-stub/build.gradle
@@ -0,0 +1,14 @@
+description = 'Conscrypt: libcore Stub'
+
+dependencies {
+    // Only compile against this. Other modules will embed the generated code directly.
+    compileOnly project(':conscrypt-constants')
+
+    compile libraries.bouncycastle_provider,
+            libraries.bouncycastle_apis,
+            libraries.junit
+}
+
+// Don't include this artifact in the distribution.
+tasks.install.enabled = false
+tasks.uploadArchives.enabled = false;
diff --git a/libcore-stub/src/main/java/android/system/StructTimeval.java b/libcore-stub/src/main/java/android/system/StructTimeval.java
new file mode 100644
index 0000000..91a6f2a
--- /dev/null
+++ b/libcore-stub/src/main/java/android/system/StructTimeval.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import libcore.util.Objects;
+
+/**
+ * Corresponds to C's {@code struct timeval} from
+ * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_time.h.html">&lt;sys/time.h&gt;</a>
+ *
+ * @hide
+ */
+public final class StructTimeval {
+    /** Seconds. */
+    public final long tv_sec;
+
+    /** Microseconds. */
+    public final long tv_usec;
+
+    private StructTimeval(long tv_sec, long tv_usec) {
+        this.tv_sec = tv_sec;
+        this.tv_usec = tv_usec;
+    }
+
+    public static StructTimeval fromMillis(long millis) {
+        long tv_sec = millis / 1000;
+        long tv_usec = (millis - (tv_sec * 1000)) * 1000;
+        return new StructTimeval(tv_sec, tv_usec);
+    }
+
+    public long toMillis() {
+        return (tv_sec * 1000) + (tv_usec / 1000);
+    }
+
+    @Override public String toString() {
+        return Objects.toString(this);
+    }
+}
diff --git a/testing/src/main/java/libcore/io/IoUtils.java b/libcore-stub/src/main/java/libcore/io/IoUtils.java
similarity index 100%
rename from testing/src/main/java/libcore/io/IoUtils.java
rename to libcore-stub/src/main/java/libcore/io/IoUtils.java
diff --git a/testing/src/main/java/libcore/io/Streams.java b/libcore-stub/src/main/java/libcore/io/Streams.java
similarity index 100%
rename from testing/src/main/java/libcore/io/Streams.java
rename to libcore-stub/src/main/java/libcore/io/Streams.java
diff --git a/testing/src/main/java/libcore/java/io/NullPrintStream.java b/libcore-stub/src/main/java/libcore/java/io/NullPrintStream.java
similarity index 100%
rename from testing/src/main/java/libcore/java/io/NullPrintStream.java
rename to libcore-stub/src/main/java/libcore/java/io/NullPrintStream.java
diff --git a/testing/src/main/java/libcore/java/security/CpuFeatures.java b/libcore-stub/src/main/java/libcore/java/security/CpuFeatures.java
similarity index 100%
rename from testing/src/main/java/libcore/java/security/CpuFeatures.java
rename to libcore-stub/src/main/java/libcore/java/security/CpuFeatures.java
diff --git a/testing/src/main/java/libcore/java/security/StandardNames.java b/libcore-stub/src/main/java/libcore/java/security/StandardNames.java
similarity index 100%
rename from testing/src/main/java/libcore/java/security/StandardNames.java
rename to libcore-stub/src/main/java/libcore/java/security/StandardNames.java
diff --git a/testing/src/main/java/libcore/java/security/TestKeyStore.java b/libcore-stub/src/main/java/libcore/java/security/TestKeyStore.java
similarity index 100%
rename from testing/src/main/java/libcore/java/security/TestKeyStore.java
rename to libcore-stub/src/main/java/libcore/java/security/TestKeyStore.java
diff --git a/testing/src/main/java/libcore/javax/net/ssl/FakeSSLSession.java b/libcore-stub/src/main/java/libcore/javax/net/ssl/FakeSSLSession.java
similarity index 100%
rename from testing/src/main/java/libcore/javax/net/ssl/FakeSSLSession.java
rename to libcore-stub/src/main/java/libcore/javax/net/ssl/FakeSSLSession.java
diff --git a/testing/src/main/java/libcore/javax/net/ssl/TestKeyManager.java b/libcore-stub/src/main/java/libcore/javax/net/ssl/TestKeyManager.java
similarity index 100%
rename from testing/src/main/java/libcore/javax/net/ssl/TestKeyManager.java
rename to libcore-stub/src/main/java/libcore/javax/net/ssl/TestKeyManager.java
diff --git a/testing/src/main/java/libcore/javax/net/ssl/TestTrustManager.java b/libcore-stub/src/main/java/libcore/javax/net/ssl/TestTrustManager.java
similarity index 100%
rename from testing/src/main/java/libcore/javax/net/ssl/TestTrustManager.java
rename to libcore-stub/src/main/java/libcore/javax/net/ssl/TestTrustManager.java
diff --git a/libcore-stub/src/main/java/libcore/net/NetworkSecurityPolicy.java b/libcore-stub/src/main/java/libcore/net/NetworkSecurityPolicy.java
new file mode 100644
index 0000000..d9c87a4
--- /dev/null
+++ b/libcore-stub/src/main/java/libcore/net/NetworkSecurityPolicy.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.net;
+
+/**
+ * Network security policy for this process/application.
+ *
+ * <p>Network stacks/components are expected to honor this policy. Components which can use the
+ * Android framework API should be accessing this policy via the framework's
+ * {@code android.security.NetworkSecurityPolicy} instead of via this class.
+ *
+ * <p>The policy currently consists of a single flag: whether cleartext network traffic is
+ * permitted. See {@link #isCleartextTrafficPermitted()}.
+ */
+public abstract class NetworkSecurityPolicy {
+
+    private static volatile NetworkSecurityPolicy instance = new DefaultNetworkSecurityPolicy();
+
+    public static NetworkSecurityPolicy getInstance() {
+        return instance;
+    }
+
+    public static void setInstance(NetworkSecurityPolicy policy) {
+        if (policy == null) {
+            throw new NullPointerException("policy == null");
+        }
+        instance = policy;
+    }
+
+    /**
+     * Returns {@code true} if cleartext network traffic (e.g. HTTP, FTP, XMPP, IMAP, SMTP --
+     * without TLS or STARTTLS) is permitted for all network communications of this process.
+     *
+     * <p>{@link #isCleartextTrafficPermitted(String)} should be used to determine if cleartext
+     * traffic is permitted for a specific host.
+     *
+     * <p>When cleartext network traffic is not permitted, the platform's components (e.g. HTTP
+     * stacks, {@code WebView}, {@code MediaPlayer}) will refuse this process's requests to use
+     * cleartext traffic. Third-party libraries are encouraged to do the same.
+     *
+     * <p>This flag is honored on a best effort basis because it's impossible to prevent all
+     * cleartext traffic from an application given the level of access provided to applications on
+     * Android. For example, there's no expectation that {@link java.net.Socket} API will honor this
+     * flag. Luckily, most network traffic from apps is handled by higher-level network stacks which
+     * can be made to honor this flag. Platform-provided network stacks (e.g. HTTP and FTP) honor
+     * this flag from day one, and well-established third-party network stacks will eventually
+     * honor it.
+     */
+    public abstract boolean isCleartextTrafficPermitted();
+
+    /**
+     * Returns {@code true} if cleartext network traffic (e.g. HTTP, FTP, XMPP, IMAP, SMTP --
+     * without TLS or STARTTLS) is permitted for communicating with {@code hostname} for this
+     * process.
+     *
+     * <p>See {@link #isCleartextTrafficPermitted} for more details.
+     */
+    public abstract boolean isCleartextTrafficPermitted(String hostname);
+
+    /**
+     * Returns {@code true} if Certificate Transparency information is required to be presented by
+     * the server and verified by the client in TLS connections to {@code hostname}.
+     *
+     * <p>See RFC6962 section 3.3 for more details.
+     */
+    public abstract boolean isCertificateTransparencyVerificationRequired(String hostname);
+
+    public static final class DefaultNetworkSecurityPolicy extends NetworkSecurityPolicy {
+        @Override
+        public boolean isCleartextTrafficPermitted() {
+            return true;
+        }
+
+        @Override
+        public boolean isCleartextTrafficPermitted(String hostname) {
+            return isCleartextTrafficPermitted();
+        }
+
+        @Override
+        public boolean isCertificateTransparencyVerificationRequired(String hostname) {
+            return false;
+        }
+    }
+}
diff --git a/libcore-stub/src/main/java/libcore/util/Objects.java b/libcore-stub/src/main/java/libcore/util/Objects.java
new file mode 100644
index 0000000..573b973
--- /dev/null
+++ b/libcore-stub/src/main/java/libcore/util/Objects.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+public final class Objects {
+    private Objects() {}
+
+    /**
+     * Returns true if two possibly-null objects are equal.
+     */
+    public static boolean equal(Object a, Object b) {
+        return a == b || (a != null && a.equals(b));
+    }
+
+    public static int hashCode(Object o) {
+        return (o == null) ? 0 : o.hashCode();
+    }
+
+    /**
+     * Returns a string reporting the value of each declared field, via reflection.
+     * Static and transient fields are automatically skipped. Produces output like
+     * "SimpleClassName[integer=1234,string="hello",character='c',intArray=[1,2,3]]".
+     */
+    public static String toString(Object o) {
+        Class<?> c = o.getClass();
+        StringBuilder sb = new StringBuilder();
+        sb.append(c.getSimpleName()).append('[');
+        int i = 0;
+        for (Field f : c.getDeclaredFields()) {
+            if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) {
+                continue;
+            }
+            f.setAccessible(true);
+            try {
+                Object value = f.get(o);
+
+                if (i++ > 0) {
+                    sb.append(',');
+                }
+
+                sb.append(f.getName());
+                sb.append('=');
+
+                if (value.getClass().isArray()) {
+                    if (value.getClass() == boolean[].class) {
+                        sb.append(Arrays.toString((boolean[]) value));
+                    } else if (value.getClass() == byte[].class) {
+                        sb.append(Arrays.toString((byte[]) value));
+                    } else if (value.getClass() == char[].class) {
+                        sb.append(Arrays.toString((char[]) value));
+                    } else if (value.getClass() == double[].class) {
+                        sb.append(Arrays.toString((double[]) value));
+                    } else if (value.getClass() == float[].class) {
+                        sb.append(Arrays.toString((float[]) value));
+                    } else if (value.getClass() == int[].class) {
+                        sb.append(Arrays.toString((int[]) value));
+                    } else if (value.getClass() == long[].class) {
+                        sb.append(Arrays.toString((long[]) value));
+                    } else if (value.getClass() == short[].class) {
+                        sb.append(Arrays.toString((short[]) value));
+                    } else {
+                        sb.append(Arrays.toString((Object[]) value));
+                    }
+                } else if (value.getClass() == Character.class) {
+                    sb.append('\'').append(value).append('\'');
+                } else if (value.getClass() == String.class) {
+                    sb.append('"').append(value).append('"');
+                } else {
+                    sb.append(value);
+                }
+            } catch (IllegalAccessException unexpected) {
+                throw new AssertionError(unexpected);
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/openjdk-benchmarks/build.gradle b/openjdk-benchmarks/build.gradle
index cfbf43d..f26196e 100644
--- a/openjdk-benchmarks/build.gradle
+++ b/openjdk-benchmarks/build.gradle
@@ -21,7 +21,6 @@
 
 dependencies {
     compile project(':conscrypt-testing'),
-            libraries.guava,
             libraries.junit,
             libraries.netty_handler,
             libraries.netty_tcnative
diff --git a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ClientSocketThroughputBenchmark.java b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ClientSocketThroughputBenchmark.java
index d98a278..d9db3f7 100644
--- a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ClientSocketThroughputBenchmark.java
+++ b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ClientSocketThroughputBenchmark.java
@@ -16,14 +16,14 @@
 
 package org.conscrypt.benchmarks;
 
-import static org.conscrypt.testing.TestUtil.LOCALHOST;
-import static org.conscrypt.testing.TestUtil.getConscryptServerSocketFactory;
-import static org.conscrypt.testing.TestUtil.getConscryptSocketFactory;
-import static org.conscrypt.testing.TestUtil.getJdkServerSocketFactory;
-import static org.conscrypt.testing.TestUtil.getJdkSocketFactory;
-import static org.conscrypt.testing.TestUtil.getProtocols;
-import static org.conscrypt.testing.TestUtil.newTextMessage;
-import static org.conscrypt.testing.TestUtil.pickUnusedPort;
+import static org.conscrypt.TestUtils.LOCALHOST;
+import static org.conscrypt.TestUtils.getConscryptServerSocketFactory;
+import static org.conscrypt.TestUtils.getConscryptSocketFactory;
+import static org.conscrypt.TestUtils.getJdkServerSocketFactory;
+import static org.conscrypt.TestUtils.getJdkSocketFactory;
+import static org.conscrypt.TestUtils.getProtocols;
+import static org.conscrypt.TestUtils.newTextMessage;
+import static org.conscrypt.TestUtils.pickUnusedPort;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -38,8 +38,8 @@
 import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
-import org.conscrypt.testing.TestClient;
-import org.conscrypt.testing.TestServer;
+import org.conscrypt.TestClient;
+import org.conscrypt.TestServer;
 import org.openjdk.jmh.annotations.AuxCounters;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.Fork;
diff --git a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ServerSocketThroughputBenchmark.java b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ServerSocketThroughputBenchmark.java
index 6b1ff21..8778da9 100644
--- a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ServerSocketThroughputBenchmark.java
+++ b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ServerSocketThroughputBenchmark.java
@@ -16,13 +16,13 @@
 
 package org.conscrypt.benchmarks;
 
-import static org.conscrypt.testing.TestUtil.LOCALHOST;
-import static org.conscrypt.testing.TestUtil.getConscryptServerSocketFactory;
-import static org.conscrypt.testing.TestUtil.getJdkServerSocketFactory;
-import static org.conscrypt.testing.TestUtil.getJdkSocketFactory;
-import static org.conscrypt.testing.TestUtil.getProtocols;
-import static org.conscrypt.testing.TestUtil.newTextMessage;
-import static org.conscrypt.testing.TestUtil.pickUnusedPort;
+import static org.conscrypt.TestUtils.LOCALHOST;
+import static org.conscrypt.TestUtils.getConscryptServerSocketFactory;
+import static org.conscrypt.TestUtils.getJdkServerSocketFactory;
+import static org.conscrypt.TestUtils.getJdkSocketFactory;
+import static org.conscrypt.TestUtils.getProtocols;
+import static org.conscrypt.TestUtils.newTextMessage;
+import static org.conscrypt.TestUtils.pickUnusedPort;
 import static org.junit.Assert.assertEquals;
 
 import java.io.IOException;
@@ -37,9 +37,9 @@
 import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
-import org.conscrypt.testing.TestClient;
-import org.conscrypt.testing.TestServer;
-import org.conscrypt.testing.TestServer.MessageProcessor;
+import org.conscrypt.TestClient;
+import org.conscrypt.TestServer;
+import org.conscrypt.TestServer.MessageProcessor;
 import org.openjdk.jmh.annotations.AuxCounters;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.Level;
diff --git a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/SslEngineBenchmark.java b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/SslEngineBenchmark.java
index 3a9deab..679dec1 100644
--- a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/SslEngineBenchmark.java
+++ b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/SslEngineBenchmark.java
@@ -33,12 +33,12 @@
 package org.conscrypt.benchmarks;
 
 import static java.lang.Math.max;
-import static org.conscrypt.testing.TestUtil.PROTOCOL_TLS_V1_2;
-import static org.conscrypt.testing.TestUtil.doEngineHandshake;
-import static org.conscrypt.testing.TestUtil.initClientSslContext;
-import static org.conscrypt.testing.TestUtil.initEngine;
-import static org.conscrypt.testing.TestUtil.initServerSslContext;
-import static org.conscrypt.testing.TestUtil.newTextMessage;
+import static org.conscrypt.TestUtils.PROTOCOL_TLS_V1_2;
+import static org.conscrypt.TestUtils.doEngineHandshake;
+import static org.conscrypt.TestUtils.initClientSslContext;
+import static org.conscrypt.TestUtils.initEngine;
+import static org.conscrypt.TestUtils.initServerSslContext;
+import static org.conscrypt.TestUtils.newTextMessage;
 import static org.junit.Assert.assertEquals;
 
 import io.netty.buffer.UnpooledByteBufAllocator;
diff --git a/openjdk/build.gradle b/openjdk/build.gradle
index 9641b5b..898e5a6 100644
--- a/openjdk/build.gradle
+++ b/openjdk/build.gradle
@@ -30,10 +30,26 @@
             srcDirs += project(':conscrypt-constants').sourceSets.main.java.srcDirs
         }
     }
+
+    platform {
+        java {
+            srcDirs = [ "src/main/java" ]
+            includes = [ "org/conscrypt/Platform.java" ]
+        }
+    }
+}
+
+task platformJar(type: Jar) {
+    from sourceSets.platform.output
 }
 
 configurations {
     publicApiDocs
+    platform
+}
+
+artifacts {
+    platform platformJar
 }
 
 // Append the BoringSSL-Version to the manifest.
@@ -51,6 +67,8 @@
                 project(':conscrypt-testing'),
                 libraries.junit,
                 libraries.mockito
+
+    platformCompileOnly sourceSets.main.output
 }
 
 javadoc {
diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java
index cf569f3..6708cda 100644
--- a/openjdk/src/main/java/org/conscrypt/Platform.java
+++ b/openjdk/src/main/java/org/conscrypt/Platform.java
@@ -63,10 +63,10 @@
     private Platform() {
     }
 
-    public static void setup() {
+    static void setup() {
     }
 
-    public static FileDescriptor getFileDescriptor(Socket s) {
+    static FileDescriptor getFileDescriptor(Socket s) {
         try {
             SocketChannel channel = s.getChannel();
             if (channel != null) {
@@ -90,11 +90,11 @@
         }
     }
 
-    public static FileDescriptor getFileDescriptorFromSSLSocket(OpenSSLSocketImpl openSSLSocketImpl) {
+    static FileDescriptor getFileDescriptorFromSSLSocket(OpenSSLSocketImpl openSSLSocketImpl) {
         return getFileDescriptor(openSSLSocketImpl);
     }
 
-    public static String getCurveName(ECParameterSpec spec) {
+    static String getCurveName(ECParameterSpec spec) {
         if (m_getCurveName == null) {
             return null;
         }
@@ -105,18 +105,18 @@
         }
     }
 
-    public static void setCurveName(ECParameterSpec spec, String curveName) {
+    static void setCurveName(ECParameterSpec spec, String curveName) {
         // This doesn't appear to be needed.
     }
 
     /*
      * Call Os.setsockoptTimeval via reflection.
      */
-    public static void setSocketWriteTimeout(Socket s, long timeoutMillis) throws SocketException {
+    static void setSocketWriteTimeout(Socket s, long timeoutMillis) throws SocketException {
         // TODO: figure this out on the RI
     }
 
-    public static void setSSLParameters(SSLParameters params, SSLParametersImpl impl,
+    static void setSSLParameters(SSLParameters params, SSLParametersImpl impl,
             OpenSSLSocketImpl socket) {
         impl.setEndpointIdentificationAlgorithm(params.getEndpointIdentificationAlgorithm());
         impl.setUseCipherSuitesOrder(params.getUseCipherSuitesOrder());
@@ -131,7 +131,7 @@
         }
     }
 
-    public static void getSSLParameters(SSLParameters params, SSLParametersImpl impl,
+    static void getSSLParameters(SSLParameters params, SSLParametersImpl impl,
             OpenSSLSocketImpl socket) {
         params.setEndpointIdentificationAlgorithm(impl.getEndpointIdentificationAlgorithm());
         params.setUseCipherSuitesOrder(impl.getUseCipherSuitesOrder());
@@ -141,7 +141,7 @@
         }
     }
 
-    public static void setSSLParameters(
+    static void setSSLParameters(
             SSLParameters params, SSLParametersImpl impl, OpenSSLEngineImpl engine) {
         impl.setEndpointIdentificationAlgorithm(params.getEndpointIdentificationAlgorithm());
         impl.setUseCipherSuitesOrder(params.getUseCipherSuitesOrder());
@@ -156,7 +156,7 @@
         }
     }
 
-    public static void getSSLParameters(
+    static void getSSLParameters(
             SSLParameters params, SSLParametersImpl impl, OpenSSLEngineImpl engine) {
         params.setEndpointIdentificationAlgorithm(impl.getEndpointIdentificationAlgorithm());
         params.setUseCipherSuitesOrder(impl.getUseCipherSuitesOrder());
@@ -179,16 +179,16 @@
         return null;
     }
 
-    public static void setEndpointIdentificationAlgorithm(SSLParameters params,
+    static void setEndpointIdentificationAlgorithm(SSLParameters params,
             String endpointIdentificationAlgorithm) {
         params.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm);
     }
 
-    public static String getEndpointIdentificationAlgorithm(SSLParameters params) {
+    static String getEndpointIdentificationAlgorithm(SSLParameters params) {
         return params.getEndpointIdentificationAlgorithm();
     }
 
-    public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain,
+    static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain,
             String authType, OpenSSLSocketImpl socket) throws CertificateException {
         if (tm instanceof X509ExtendedTrustManager) {
             X509ExtendedTrustManager x509etm = (X509ExtendedTrustManager) tm;
@@ -198,7 +198,7 @@
         }
     }
 
-    public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
+    static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
             String authType, OpenSSLSocketImpl socket) throws CertificateException {
         if (tm instanceof X509ExtendedTrustManager) {
             X509ExtendedTrustManager x509etm = (X509ExtendedTrustManager) tm;
@@ -208,7 +208,7 @@
         }
     }
 
-    public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain,
+    static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain,
             String authType, OpenSSLEngineImpl engine) throws CertificateException {
         if (tm instanceof X509ExtendedTrustManager) {
             X509ExtendedTrustManager x509etm = (X509ExtendedTrustManager) tm;
@@ -218,7 +218,7 @@
         }
     }
 
-    public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
+    static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
             String authType, OpenSSLEngineImpl engine) throws CertificateException {
         if (tm instanceof X509ExtendedTrustManager) {
             X509ExtendedTrustManager x509etm = (X509ExtendedTrustManager) tm;
@@ -231,20 +231,20 @@
     /**
      * Wraps an old AndroidOpenSSL key instance. This is not needed on RI.
      */
-    public static OpenSSLKey wrapRsaKey(PrivateKey javaKey) {
+    static OpenSSLKey wrapRsaKey(PrivateKey javaKey) {
         return null;
     }
 
     /**
      * Logs to the system EventLog system.
      */
-    public static void logEvent(String message) {
+    static void logEvent(String message) {
     }
 
     /**
      * Returns true if the supplied hostname is an literal IP address.
      */
-    public static boolean isLiteralIpAddress(String hostname) {
+    static boolean isLiteralIpAddress(String hostname) {
         // TODO: any RI API to make this better?
         return AddressUtils.isLiteralIpAddress(hostname);
     }
@@ -252,21 +252,21 @@
     /**
      * For unbundled versions, SNI is always enabled by default.
      */
-    public static boolean isSniEnabledByDefault() {
+    static boolean isSniEnabledByDefault() {
         return true;
     }
 
     /**
      * Currently we don't wrap anything from the RI.
      */
-    public static SSLSocketFactory wrapSocketFactoryIfNeeded(OpenSSLSocketFactoryImpl factory) {
+    static SSLSocketFactory wrapSocketFactoryIfNeeded(OpenSSLSocketFactoryImpl factory) {
         return factory;
     }
 
     /**
      * Convert from platform's GCMParameterSpec to our internal version.
      */
-    public static GCMParameters fromGCMParameterSpec(AlgorithmParameterSpec params) {
+    static GCMParameters fromGCMParameterSpec(AlgorithmParameterSpec params) {
         if (params instanceof GCMParameterSpec) {
             GCMParameterSpec gcmParams = (GCMParameterSpec) params;
             return new GCMParameters(gcmParams.getTLen(), gcmParams.getIV());
@@ -277,7 +277,7 @@
     /**
      * Creates a platform version of {@code GCMParameterSpec}.
      */
-    public static AlgorithmParameterSpec toGCMParameterSpec(int tagLenInBits, byte[] iv) {
+    static AlgorithmParameterSpec toGCMParameterSpec(int tagLenInBits, byte[] iv) {
         return new GCMParameterSpec(tagLenInBits, iv);
     }
 
@@ -285,30 +285,30 @@
      * CloseGuard functions.
      */
 
-    public static Object closeGuardGet() {
+    static Object closeGuardGet() {
         return null;
     }
 
-    public static void closeGuardOpen(Object guardObj, String message) {
+    static void closeGuardOpen(Object guardObj, String message) {
     }
 
-    public static void closeGuardClose(Object guardObj) {
+    static void closeGuardClose(Object guardObj) {
     }
 
-    public static void closeGuardWarnIfOpen(Object guardObj) {
+    static void closeGuardWarnIfOpen(Object guardObj) {
     }
 
     /*
      * BlockGuard functions.
      */
 
-    public static void blockGuardOnNetwork() {
+    static void blockGuardOnNetwork() {
     }
 
     /**
      * OID to Algorithm Name mapping.
      */
-    public static String oidToAlgorithmName(String oid) {
+    static String oidToAlgorithmName(String oid) {
         try {
             return AlgorithmId.get(oid).getName();
         } catch (NoSuchAlgorithmException e) {
@@ -320,11 +320,11 @@
      * Pre-Java-8 backward compatibility.
      */
 
-    public static SSLSession wrapSSLSession(AbstractOpenSSLSession sslSession) {
+    static SSLSession wrapSSLSession(AbstractOpenSSLSession sslSession) {
         return new OpenSSLExtendedSessionImpl(sslSession);
     }
 
-    public static SSLSession unwrapSSLSession(SSLSession sslSession) {
+    static SSLSession unwrapSSLSession(SSLSession sslSession) {
         if (sslSession instanceof OpenSSLExtendedSessionImpl) {
             return ((OpenSSLExtendedSessionImpl) sslSession).getDelegate();
         }
@@ -335,7 +335,7 @@
      * Pre-Java-7 backward compatibility.
      */
 
-    public static String getHostStringFromInetSocketAddress(InetSocketAddress addr) {
+    static String getHostStringFromInetSocketAddress(InetSocketAddress addr) {
         return addr.getHostString();
     }
 
@@ -355,7 +355,7 @@
      * - conscrypt.ct.enforce.com.*
      * - conscrypt.ct.enforce.*
      */
-    public static boolean isCTVerificationRequired(String hostname) {
+    static boolean isCTVerificationRequired(String hostname) {
         if (hostname == null) {
             return false;
         }
diff --git a/openjdk/src/test/java/org/conscrypt/OpenSSLEngineImplTest.java b/openjdk/src/test/java/org/conscrypt/OpenSSLEngineImplTest.java
index af62889..188ee47 100644
--- a/openjdk/src/test/java/org/conscrypt/OpenSSLEngineImplTest.java
+++ b/openjdk/src/test/java/org/conscrypt/OpenSSLEngineImplTest.java
@@ -16,10 +16,10 @@
 
 package org.conscrypt;
 
-import static org.conscrypt.testing.TestUtil.PROTOCOL_TLS_V1_2;
-import static org.conscrypt.testing.TestUtil.initEngine;
-import static org.conscrypt.testing.TestUtil.initSslContext;
-import static org.conscrypt.testing.TestUtil.newTextMessage;
+import static org.conscrypt.TestUtils.PROTOCOL_TLS_V1_2;
+import static org.conscrypt.TestUtils.initEngine;
+import static org.conscrypt.TestUtils.initSslContext;
+import static org.conscrypt.TestUtils.newTextMessage;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
@@ -33,7 +33,6 @@
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLHandshakeException;
 import libcore.java.security.TestKeyStore;
-import org.conscrypt.testing.TestUtil;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -145,7 +144,7 @@
     @Test
     public void exchangeMessages() throws Exception {
         setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer());
-        TestUtil.doEngineHandshake(clientEngine, serverEngine);
+        TestUtils.doEngineHandshake(clientEngine, serverEngine);
 
         ByteBuffer clientCleartextBuffer = bufferType.newBuffer(MESSAGE_SIZE);
         clientCleartextBuffer.put(newTextMessage(MESSAGE_SIZE));
@@ -186,7 +185,7 @@
     private void doMutualAuthHandshake(TestKeyStore clientKs, TestKeyStore serverKs, ClientAuth clientAuth) throws Exception {
         setupEngines(clientKs, serverKs);
         clientAuth.apply(serverEngine);
-        TestUtil.doEngineHandshake(clientEngine, serverEngine);
+        TestUtils.doEngineHandshake(clientEngine, serverEngine);
         assertEquals(HandshakeStatus.NOT_HANDSHAKING, clientEngine.getHandshakeStatus());
         assertEquals(HandshakeStatus.NOT_HANDSHAKING, serverEngine.getHandshakeStatus());
     }
diff --git a/openjdk/src/test/java/org/conscrypt/OpenSSLServerSocketImplTest.java b/openjdk/src/test/java/org/conscrypt/OpenSSLServerSocketImplTest.java
index c581247..d35b719 100644
--- a/openjdk/src/test/java/org/conscrypt/OpenSSLServerSocketImplTest.java
+++ b/openjdk/src/test/java/org/conscrypt/OpenSSLServerSocketImplTest.java
@@ -16,12 +16,12 @@
 
 package org.conscrypt;
 
-import static org.conscrypt.testing.TestUtil.LOCALHOST;
-import static org.conscrypt.testing.TestUtil.getConscryptServerSocketFactory;
-import static org.conscrypt.testing.TestUtil.getJdkSocketFactory;
-import static org.conscrypt.testing.TestUtil.getProtocols;
-import static org.conscrypt.testing.TestUtil.newTextMessage;
-import static org.conscrypt.testing.TestUtil.pickUnusedPort;
+import static org.conscrypt.TestUtils.LOCALHOST;
+import static org.conscrypt.TestUtils.getConscryptServerSocketFactory;
+import static org.conscrypt.TestUtils.getJdkSocketFactory;
+import static org.conscrypt.TestUtils.getProtocols;
+import static org.conscrypt.TestUtils.newTextMessage;
+import static org.conscrypt.TestUtils.pickUnusedPort;
 import static org.junit.Assert.assertArrayEquals;
 
 import java.io.IOException;
@@ -32,8 +32,6 @@
 import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
-import org.conscrypt.testing.TestClient;
-import org.conscrypt.testing.TestServer;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/openjdk/src/test/java/org/conscrypt/TestUtils.java b/openjdk/src/test/java/org/conscrypt/TestUtils.java
deleted file mode 100644
index 5e640fc..0000000
--- a/openjdk/src/test/java/org/conscrypt/TestUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.conscrypt;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import libcore.io.Streams;
-
-public class TestUtils {
-    private TestUtils() {}
-
-    public static InputStream openTestFile(String name) throws FileNotFoundException {
-        InputStream is = TestUtils.class.getResourceAsStream("/" + name);
-        if (is == null) {
-            throw new FileNotFoundException(name);
-        }
-        return is;
-    }
-
-    public static byte[] readTestFile(String name) throws IOException {
-        return Streams.readFully(openTestFile(name));
-    }
-}
diff --git a/platform/build.gradle b/platform/build.gradle
new file mode 100644
index 0000000..78c8f1b
--- /dev/null
+++ b/platform/build.gradle
@@ -0,0 +1,118 @@
+buildscript {
+    repositories {
+        mavenCentral()
+        mavenLocal()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.2.3' // jcenter has the latest
+        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+    }
+}
+
+description = 'Conscrypt: Android Platform'
+
+ext {
+    androidHome = "$System.env.ANDROID_HOME"
+    androidSdkInstalled = file("$androidHome").exists()
+    androidVersionCode = 1
+    androidVersionName = "$version"
+    androidMinSdkVersion = 24
+    androidTargetSdkVersion = 25
+    androidBuildToolsVersion = "25.0.0"
+}
+
+if (androidSdkInstalled) {
+    apply plugin: 'com.android.library'
+
+    android {
+        compileSdkVersion androidTargetSdkVersion
+        buildToolsVersion androidBuildToolsVersion
+
+        compileOptions {
+            sourceCompatibility androidMinJavaVersion;
+            targetCompatibility androidMinJavaVersion
+        }
+
+        defaultConfig {
+            minSdkVersion androidMinSdkVersion
+            targetSdkVersion androidTargetSdkVersion
+            versionCode androidVersionCode
+            versionName androidVersionName
+
+            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+            consumerProguardFiles 'proguard-rules.pro'
+
+            externalNativeBuild {
+                cmake {
+                    arguments '-DANDROID=True',
+                            '-DANDROID_STL=c++_static',
+                            "-DBORINGSSL_HOME=$boringsslHome"
+                    cFlags '-fvisibility=hidden',
+                            '-DBORINGSSL_SHARED_LIBRARY',
+                            '-DBORINGSSL_IMPLEMENTATION',
+                            '-DOPENSSL_SMALL',
+                            '-D_XOPEN_SOURCE=700',
+                            '-Wno-unused-parameter'
+                }
+            }
+            ndk {
+                abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
+            }
+        }
+        buildTypes {
+            release {
+                minifyEnabled false
+                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+            }
+        }
+        sourceSets {
+            main {
+                java {
+                    srcDirs = [
+                            "${rootDir}/common/src/main/java",
+                            "src/main/java",
+                    ]
+                    excludes = [ 'org/conscrypt/Platform.java' ]
+                }
+            }
+        }
+        lintOptions {
+            lintConfig file('lint.xml')
+        }
+    }
+
+    configurations {
+        publicApiDocs
+    }
+
+    dependencies {
+        compile fileTree(dir: 'libs', include: ['*.jar'])
+        compile project(path: ':conscrypt-openjdk', configuration: 'platform')
+        publicApiDocs project(':conscrypt-api-doclet')
+        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+            exclude group: 'com.android.support', module: 'support-annotations'
+        })
+        testCompile project(':conscrypt-testing'),
+                    libraries.junit
+        provided project(':conscrypt-android-stub'),
+                 project(':conscrypt-libcore-stub')
+
+        // Adds the constants module as a dependency so that we can include its generated source
+        provided project(':conscrypt-constants')
+    }
+
+    // Disable running the tests.
+    tasks.withType(Test){
+        enabled = false
+    }
+
+} else {
+    logger.warn('Android SDK has not been detected. The Android Platform module will not be built.')
+
+    // Disable all tasks
+    tasks.collect {
+        it.enabled = false
+    }
+}
diff --git a/platform/proguard-rules.pro b/platform/proguard-rules.pro
new file mode 100644
index 0000000..c3bdd2c
--- /dev/null
+++ b/platform/proguard-rules.pro
@@ -0,0 +1,24 @@
+# Add project specific ProGuard rules here.
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Many of the Conscrypt classes are referenced indirectly via JNI or
+# reflection.
+# This could probably be tightened up, but this will get it building for now.
+# TODO(kroot): Need anything special to prevent obfuscation?
+-keep class org.conscrypt.** { *; }
+
+# Backward compatibility code.
+-dontnote libcore.io.Libcore
+-dontnote org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey
+-dontnote org.apache.harmony.security.utils.AlgNameMapper
+-dontnote sun.security.x509.AlgorithmId
+
+-dontwarn dalvik.system.BlockGuard
+-dontwarn dalvik.system.BlockGuard$Policy
+-dontwarn dalvik.system.CloseGuard
+-dontwarn com.android.org.conscrypt.OpenSSLSocketImpl
+-dontwarn org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl
diff --git a/platform/src/main/AndroidManifest.xml b/platform/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ae4f1d7
--- /dev/null
+++ b/platform/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.conscrypt">
+
+    <application/>
+
+</manifest>
diff --git a/platform/src/main/java/org/conscrypt/CertBlacklist.java b/platform/src/main/java/org/conscrypt/CertBlacklist.java
index 8db2690..99e599e 100644
--- a/platform/src/main/java/org/conscrypt/CertBlacklist.java
+++ b/platform/src/main/java/org/conscrypt/CertBlacklist.java
@@ -16,6 +16,8 @@
 
 package org.conscrypt;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.FileNotFoundException;
@@ -32,7 +34,7 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-public class CertBlacklist {
+public final class CertBlacklist {
     private static final Logger logger = Logger.getLogger(CertBlacklist.class.getName());
 
     private final Set<BigInteger> serialBlacklist;
@@ -121,7 +123,7 @@
         }
     }
 
-    private static final Set<BigInteger> readSerialBlackList(String path) {
+    private static Set<BigInteger> readSerialBlackList(String path) {
 
         /* Start out with a base set of known bad values.
          *
@@ -163,42 +165,42 @@
         return Collections.unmodifiableSet(bl);
     }
 
-    private static final Set<byte[]> readPublicKeyBlackList(String path) {
+    private static Set<byte[]> readPublicKeyBlackList(String path) {
 
         // start out with a base set of known bad values
         Set<byte[]> bl = new HashSet<byte[]>(Arrays.asList(
             // Blacklist test cert for CTS. The cert and key can be found in
             // src/test/resources/blacklist_test_ca.pem and
             // src/test/resources/blacklist_test_ca_key.pem.
-            "bae78e6bed65a2bf60ddedde7fd91e825865e93d".getBytes(),
+            "bae78e6bed65a2bf60ddedde7fd91e825865e93d".getBytes(UTF_8),
             // From http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
             // C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info@diginotar.nl
-            "410f36363258f30b347d12ce4863e433437806a8".getBytes(),
+            "410f36363258f30b347d12ce4863e433437806a8".getBytes(UTF_8),
             // Subject: CN=DigiNotar Cyber CA
             // Issuer: CN=GTE CyberTrust Global Root
-            "ba3e7bd38cd7e1e6b9cd4c219962e59d7a2f4e37".getBytes(),
+            "ba3e7bd38cd7e1e6b9cd4c219962e59d7a2f4e37".getBytes(UTF_8),
             // Subject: CN=DigiNotar Services 1024 CA
             // Issuer: CN=Entrust.net
-            "e23b8d105f87710a68d9248050ebefc627be4ca6".getBytes(),
+            "e23b8d105f87710a68d9248050ebefc627be4ca6".getBytes(UTF_8),
             // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2
             // Issuer: CN=Staat der Nederlanden Organisatie CA - G2
-            "7b2e16bc39bcd72b456e9f055d1de615b74945db".getBytes(),
+            "7b2e16bc39bcd72b456e9f055d1de615b74945db".getBytes(UTF_8),
             // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven
             // Issuer: CN=Staat der Nederlanden Overheid CA
-            "e8f91200c65cee16e039b9f883841661635f81c5".getBytes(),
+            "e8f91200c65cee16e039b9f883841661635f81c5".getBytes(UTF_8),
             // From http://src.chromium.org/viewvc/chrome?view=rev&revision=108479
             // Subject: O=Digicert Sdn. Bhd.
             // Issuer: CN=GTE CyberTrust Global Root
-            "0129bcd5b448ae8d2496d1c3e19723919088e152".getBytes(),
+            "0129bcd5b448ae8d2496d1c3e19723919088e152".getBytes(UTF_8),
             // Subject: CN=e-islem.kktcmerkezbankasi.org/emailAddress=ileti@kktcmerkezbankasi.org
             // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
-            "5f3ab33d55007054bc5e3e5553cd8d8465d77c61".getBytes(),
+            "5f3ab33d55007054bc5e3e5553cd8d8465d77c61".getBytes(UTF_8),
             // Subject: CN=*.EGO.GOV.TR 93
             // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
-            "783333c9687df63377efceddd82efa9101913e8e".getBytes(),
+            "783333c9687df63377efceddd82efa9101913e8e".getBytes(UTF_8),
             // Subject: Subject: C=FR, O=DG Tr\xC3\xA9sor, CN=AC DG Tr\xC3\xA9sor SSL
             // Issuer: C=FR, O=DGTPE, CN=AC DGTPE Signature Authentification
-            "3ecf4bbbe46096d514bb539bb913d77aa4ef31bf".getBytes()
+            "3ecf4bbbe46096d514bb539bb913d77aa4ef31bf".getBytes(UTF_8)
         ));
 
         // attempt to augment it with values taken from gservices
@@ -207,7 +209,7 @@
             for (String value : pubkeyBlacklist.split(",")) {
                 value = value.trim();
                 if (isPubkeyHash(value)) {
-                    bl.add(value.getBytes());
+                    bl.add(value.getBytes(UTF_8));
                 } else {
                     logger.log(Level.WARNING, "Tried to blacklist invalid pubkey " + value);
                 }
@@ -239,7 +241,7 @@
         (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a',
         (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'};
 
-    private static final byte[] toHex(byte[] in) {
+    private static byte[] toHex(byte[] in) {
         byte[] out = new byte[in.length * 2];
         int outIndex = 0;
         for (int i = 0; i < in.length; i++) {
diff --git a/platform/src/main/java/org/conscrypt/CertificatePriorityComparator.java b/platform/src/main/java/org/conscrypt/CertificatePriorityComparator.java
index 95e3c5c..bdea764 100644
--- a/platform/src/main/java/org/conscrypt/CertificatePriorityComparator.java
+++ b/platform/src/main/java/org/conscrypt/CertificatePriorityComparator.java
@@ -58,7 +58,7 @@
     private static final Integer PRIORITY_SHA512 = 1;
     private static final Integer PRIORITY_UNKNOWN = -1;
     static {
-        ALGORITHM_OID_PRIORITY_MAP = new HashMap<String, Integer>();
+        ALGORITHM_OID_PRIORITY_MAP = new HashMap<>();
         // RSA oids
         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.13", PRIORITY_SHA512);
         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.12", PRIORITY_SHA384);
diff --git a/platform/src/main/java/org/conscrypt/Hex.java b/platform/src/main/java/org/conscrypt/Hex.java
index 2bd17a9..f28dd6c 100644
--- a/platform/src/main/java/org/conscrypt/Hex.java
+++ b/platform/src/main/java/org/conscrypt/Hex.java
@@ -20,12 +20,15 @@
  *
  * Helper class for dealing with hexadecimal strings.
  *
+ * @hide
  */
+@Internal
 // public for testing by TrustedCertificateStoreTest
-public class Hex {
+public final class Hex {
     private Hex() {}
 
-    private final static char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+    private final static char[] DIGITS = {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
 
     public static String bytesToHexString(byte[] bytes) {
         char[] buf = new char[bytes.length * 2];
@@ -48,5 +51,4 @@
 
         return new String(buf, cursor, bufLen - cursor);
     }
-
 }
diff --git a/platform/src/main/java/org/conscrypt/TrustManagerImpl.java b/platform/src/main/java/org/conscrypt/TrustManagerImpl.java
index 590f739..f481800 100644
--- a/platform/src/main/java/org/conscrypt/TrustManagerImpl.java
+++ b/platform/src/main/java/org/conscrypt/TrustManagerImpl.java
@@ -842,6 +842,7 @@
             return SUPPORTED_EXTENSIONS;
         }
 
+        @SuppressWarnings("ReferenceEquality")
         @Override
         public void check(Certificate c, Collection<String> unresolvedCritExts)
                 throws CertPathValidatorException {
diff --git a/platform/src/main/res/values/strings.xml b/platform/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8542005
--- /dev/null
+++ b/platform/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+<resources>
+</resources>
diff --git a/platform/src/test/java/org/conscrypt/NativeCryptoTest.java b/platform/src/test/java/org/conscrypt/NativeCryptoTest.java
index fcd5595..5384041 100644
--- a/platform/src/test/java/org/conscrypt/NativeCryptoTest.java
+++ b/platform/src/test/java/org/conscrypt/NativeCryptoTest.java
@@ -53,6 +53,7 @@
 import java.security.spec.ECPrivateKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.Callable;
@@ -86,10 +87,8 @@
     private static final long TIMEOUT_SECONDS = 5;
 
     private static OpenSSLKey SERVER_PRIVATE_KEY;
-    private static OpenSSLX509Certificate[] SERVER_CERTIFICATES_HOLDER;
     private static long[] SERVER_CERTIFICATES;
     private static OpenSSLKey CLIENT_PRIVATE_KEY;
-    private static OpenSSLX509Certificate[] CLIENT_CERTIFICATES_HOLDER;
     private static long[] CLIENT_CERTIFICATES;
     private static byte[][] CA_PRINCIPALS;
     private static OpenSSLKey CHANNEL_ID_PRIVATE_KEY;
@@ -147,16 +146,16 @@
             PrivateKeyEntry serverPrivateKeyEntry =
                     TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
             SERVER_PRIVATE_KEY = OpenSSLKey.fromPrivateKey(serverPrivateKeyEntry.getPrivateKey());
-            SERVER_CERTIFICATES_HOLDER =
-                    encodeCertificateList(serverPrivateKeyEntry.getCertificateChain());
-            SERVER_CERTIFICATES = getCertificateReferences(SERVER_CERTIFICATES_HOLDER);
+            OpenSSLX509Certificate[] serverCertificatesHolder = encodeCertificateList(
+                    serverPrivateKeyEntry.getCertificateChain());
+            SERVER_CERTIFICATES = getCertificateReferences(serverCertificatesHolder);
 
             PrivateKeyEntry clientPrivateKeyEntry =
                     TestKeyStore.getClientCertificate().getPrivateKey("RSA", "RSA");
             CLIENT_PRIVATE_KEY = OpenSSLKey.fromPrivateKey(clientPrivateKeyEntry.getPrivateKey());
-            CLIENT_CERTIFICATES_HOLDER =
-                    encodeCertificateList(clientPrivateKeyEntry.getCertificateChain());
-            CLIENT_CERTIFICATES = getCertificateReferences(CLIENT_CERTIFICATES_HOLDER);
+            OpenSSLX509Certificate[] clientCertificatesHolder = encodeCertificateList(
+                    clientPrivateKeyEntry.getCertificateChain());
+            CLIENT_CERTIFICATES = getCertificateReferences(clientCertificatesHolder);
 
             KeyStore ks = TestKeyStore.getClient().keyStore;
             String caCertAlias = ks.aliases().nextElement();
@@ -268,6 +267,7 @@
             NativeCrypto.EVP_PKEY_cmp(pkey1, null);
             fail("Should throw NullPointerException when arguments are NULL");
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         assertEquals("Same keys should be the equal", 1, NativeCrypto.EVP_PKEY_cmp(pkey1, pkey1));
@@ -324,7 +324,9 @@
             NativeCrypto.SSL_CTX_set_session_id_context(c, new byte[32]);
             try {
                 NativeCrypto.SSL_CTX_set_session_id_context(c, new byte[33]);
+                fail("Expected IllegalArgumentException");
             } catch (IllegalArgumentException expected) {
+                // Expected.
             }
         } finally {
             NativeCrypto.SSL_CTX_free(c);
@@ -364,6 +366,7 @@
             NativeCrypto.SSL_use_certificate(s, null);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         NativeCrypto.SSL_use_certificate(s, getServerCertificates());
@@ -380,6 +383,7 @@
             NativeCrypto.SSL_set1_tls_channel_id(NULL, null);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -389,6 +393,7 @@
             NativeCrypto.SSL_set1_tls_channel_id(s, null);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         // Use the key natively. This works because the initChannelIdKey method ensures that the
@@ -405,6 +410,7 @@
             NativeCrypto.SSL_use_PrivateKey(NULL, null);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -414,6 +420,7 @@
             NativeCrypto.SSL_use_PrivateKey(s, null);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         NativeCrypto.SSL_use_PrivateKey(s, getServerPrivateKey().getNativeRef());
@@ -437,6 +444,7 @@
             NativeCrypto.SSL_check_private_key(s);
             fail();
         } catch (SSLException expected) {
+            // Expected.
         }
 
         NativeCrypto.SSL_free(s);
@@ -455,6 +463,7 @@
             NativeCrypto.SSL_check_private_key(s);
             fail();
         } catch (SSLException expected) {
+            // Expected.
         }
 
         NativeCrypto.SSL_use_PrivateKey(s, getServerPrivateKey().getNativeRef());
@@ -475,6 +484,7 @@
             NativeCrypto.SSL_check_private_key(s);
             fail();
         } catch (SSLException expected) {
+            // Expected.
         }
 
         NativeCrypto.SSL_use_certificate(s, getServerCertificates());
@@ -490,6 +500,7 @@
             NativeCrypto.SSL_get_mode(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -505,6 +516,7 @@
             NativeCrypto.SSL_set_mode(NULL, 0);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -532,6 +544,7 @@
             NativeCrypto.SSL_get_options(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -547,6 +560,7 @@
             NativeCrypto.SSL_set_options(NULL, 0);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -564,6 +578,7 @@
             NativeCrypto.SSL_clear_options(NULL, 0);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -583,6 +598,7 @@
             NativeCrypto.SSL_set_cipher_lists(NULL, null);
             fail("Exception not thrown for null ssl and null list");
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -592,6 +608,7 @@
             NativeCrypto.SSL_set_cipher_lists(s, null);
             fail("Exception not thrown for null list");
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         // Explicitly checking that the empty list is allowed.
@@ -602,6 +619,7 @@
             NativeCrypto.SSL_set_cipher_lists(s, new String[] {null});
             fail("Exception not thrown for list with null element");
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         // see OpenSSL ciphers man page
@@ -617,6 +635,7 @@
                 NativeCrypto.SSL_set_cipher_lists(s, new String[] {illegal});
                 fail("Exception now thrown for illegal cipher: " + illegal);
             } catch (IllegalArgumentException expected) {
+                // Expected.
             }
         }
 
@@ -634,6 +653,7 @@
             NativeCrypto.SSL_set_verify(NULL, 0);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -657,11 +677,12 @@
         protected List<String> enabledCipherSuites;
 
         /**
-         * @throws SSLException
+         * @throws SSLException if an error occurs creating the context.
          */
         public long getContext() throws SSLException {
             return NativeCrypto.SSL_CTX_new();
         }
+
         public long beforeHandshake(long context) throws SSLException {
             long s = NativeCrypto.SSL_new(context);
             // Limit cipher suites to a known set so authMethod is known.
@@ -697,6 +718,7 @@
                 try {
                     NativeCrypto.SSL_shutdown(ssl, fd, callback);
                 } catch (IOException e) {
+                    // Expected.
                 }
                 NativeCrypto.SSL_free(ssl);
             }
@@ -748,8 +770,8 @@
             if (DEBUG) {
                 System.out.println("ssl=0x" + Long.toString(sslNativePointer, 16)
                         + " clientCertificateRequested"
-                        + " keyTypes=" + keyTypes + " asn1DerEncodedX500Principals="
-                        + asn1DerEncodedX500Principals);
+                        + " keyTypes=" + Arrays.toString(keyTypes) + " asn1DerEncodedX500Principals="
+                        + Arrays.toString(asn1DerEncodedX500Principals));
             }
             this.keyTypes = keyTypes;
             this.asn1DerEncodedX500Principals = asn1DerEncodedX500Principals;
@@ -991,12 +1013,14 @@
             NativeCrypto.SSL_do_handshake(s, null, null, 0);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         try {
             NativeCrypto.SSL_do_handshake(s, INVALID_FD, null, 0);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         NativeCrypto.SSL_free(s);
@@ -1157,7 +1181,7 @@
         Hooks cHooks = new Hooks();
         cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY;
         // TLS Channel ID currently requires ECDHE-based key exchanges.
-        cHooks.enabledCipherSuites = Arrays.asList(new String[] {"ECDHE-RSA-AES128-SHA"});
+        cHooks.enabledCipherSuites = Collections.singletonList("ECDHE-RSA-AES128-SHA");
         ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         sHooks.channelIdEnabled = true;
         sHooks.enabledCipherSuites = cHooks.enabledCipherSuites;
@@ -1190,7 +1214,7 @@
         Hooks cHooks = new Hooks();
         cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY;
         // TLS Channel ID currently requires ECDHE-based key exchanges.
-        cHooks.enabledCipherSuites = Arrays.asList(new String[] {"ECDHE-RSA-AES128-SHA"});
+        cHooks.enabledCipherSuites = Collections.singletonList("ECDHE-RSA-AES128-SHA");
         ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         sHooks.channelIdEnabled = false;
         sHooks.enabledCipherSuites = cHooks.enabledCipherSuites;
@@ -1223,7 +1247,7 @@
         Hooks cHooks = new Hooks();
         cHooks.channelIdPrivateKey = null;
         // TLS Channel ID currently requires ECDHE-based key exchanges.
-        cHooks.enabledCipherSuites = Arrays.asList(new String[] {"ECDHE-RSA-AES128-SHA"});
+        cHooks.enabledCipherSuites = Collections.singletonList("ECDHE-RSA-AES128-SHA");
         ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         sHooks.channelIdEnabled = true;
         sHooks.enabledCipherSuites = cHooks.enabledCipherSuites;
@@ -1536,6 +1560,7 @@
                 NativeCrypto.SSL_use_psk_identity_hint(s, pskIdentityHint.toString());
                 fail();
             } catch (SSLException expected) {
+                // Expected.
             }
         } finally {
             NativeCrypto.SSL_free(s);
@@ -1549,6 +1574,7 @@
             NativeCrypto.SSL_set_session(NULL, NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         {
@@ -1649,6 +1675,7 @@
             NativeCrypto.SSL_set_session_creation_enabled(NULL, false);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         {
@@ -1726,6 +1753,7 @@
             NativeCrypto.SSL_set_tlsext_host_name(NULL, null);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         final String hostname = "www.android.com";
@@ -1739,6 +1767,7 @@
                 NativeCrypto.SSL_set_tlsext_host_name(s, null);
                 fail();
             } catch (NullPointerException expected) {
+                // Expected.
             }
 
             // too long hostname
@@ -1748,6 +1777,7 @@
                 NativeCrypto.SSL_set_tlsext_host_name(s, new String(longHostname));
                 fail();
             } catch (SSLException expected) {
+                // Expected
             }
 
             assertNull(NativeCrypto.SSL_get_servername(s));
@@ -1798,7 +1828,7 @@
             public void afterHandshake(long session, long ssl, long context, Socket socket,
                     FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception {
                 byte[] negotiated = NativeCrypto.SSL_get0_alpn_selected(ssl);
-                assertEquals("spdy/2", new String(negotiated));
+                assertEquals("spdy/2", new String(negotiated, "UTF-8"));
                 super.afterHandshake(session, ssl, context, socket, fd, callback);
             }
         };
@@ -1807,7 +1837,7 @@
             public void afterHandshake(long session, long ssl, long c, Socket sock,
                     FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception {
                 byte[] negotiated = NativeCrypto.SSL_get0_alpn_selected(ssl);
-                assertEquals("spdy/2", new String(negotiated));
+                assertEquals("spdy/2", new String(negotiated, "UTF-8"));
                 super.afterHandshake(session, ssl, c, sock, fd, callback);
             }
         };
@@ -1828,6 +1858,7 @@
             NativeCrypto.SSL_get_servername(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -1845,6 +1876,7 @@
             NativeCrypto.SSL_renegotiate(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         final ServerSocket listener = new ServerSocket(0);
@@ -1871,6 +1903,7 @@
                     NativeCrypto.SSL_renegotiate(s);
                     fail();
                 } catch (SSLException expected) {
+                    // Expected.
                 }
                 NativeCrypto.SSL_write(s, fd, callback, new byte[] {42}, 0, 1, 0);
                 super.afterHandshake(session, s, c, sock, fd, callback);
@@ -1888,6 +1921,7 @@
             NativeCrypto.SSL_get_certificate(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         final ServerSocket listener = new ServerSocket(0);
@@ -1920,6 +1954,7 @@
             NativeCrypto.SSL_get_peer_cert_chain(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         final ServerSocket listener = new ServerSocket(0);
@@ -1952,6 +1987,7 @@
             NativeCrypto.SSL_read(NULL, null, null, null, 0, 0, 0);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         // null FileDescriptor
@@ -1962,6 +1998,7 @@
                 NativeCrypto.SSL_read(s, null, DUMMY_CB, null, 0, 0, 0);
                 fail();
             } catch (NullPointerException expected) {
+                // Expected.
             }
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
@@ -1975,6 +2012,7 @@
                 NativeCrypto.SSL_read(s, INVALID_FD, null, null, 0, 0, 0);
                 fail();
             } catch (NullPointerException expected) {
+                // Expected.
             }
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
@@ -1988,6 +2026,7 @@
                 NativeCrypto.SSL_read(s, INVALID_FD, DUMMY_CB, null, 0, 0, 0);
                 fail();
             } catch (NullPointerException expected) {
+                // Expected.
             }
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
@@ -2001,6 +2040,7 @@
                 NativeCrypto.SSL_read(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0);
                 fail();
             } catch (SSLException expected) {
+                // Expected.
             }
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
@@ -2071,6 +2111,7 @@
             NativeCrypto.SSL_write(NULL, null, null, null, 0, 0, 0);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         // null FileDescriptor
@@ -2081,6 +2122,7 @@
                 NativeCrypto.SSL_write(s, null, DUMMY_CB, null, 0, 1, 0);
                 fail();
             } catch (NullPointerException expected) {
+                // Expected.
             }
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
@@ -2094,6 +2136,7 @@
                 NativeCrypto.SSL_write(s, INVALID_FD, null, null, 0, 1, 0);
                 fail();
             } catch (NullPointerException expected) {
+                // Expected.
             }
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
@@ -2107,6 +2150,7 @@
                 NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, null, 0, 1, 0);
                 fail();
             } catch (NullPointerException expected) {
+                // Expected.
             }
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
@@ -2120,6 +2164,7 @@
                 NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0);
                 fail();
             } catch (SSLException expected) {
+                // Expected.
             }
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
@@ -2160,9 +2205,10 @@
                     @Override
                     public void run() {
                         try {
-                            Thread.sleep(1 * 1000);
+                            Thread.sleep(1000);
                             NativeCrypto.SSL_interrupt(s);
                         } catch (Exception e) {
+                            // Expected.
                         }
                     }
                 }.start();
@@ -2201,6 +2247,7 @@
                     NativeCrypto.SSL_shutdown(sslSession, null, DUMMY_CB);
                     fail();
                 } catch (NullPointerException expected) {
+                    // Expected.
                 }
             }
         });
@@ -2213,6 +2260,7 @@
                     NativeCrypto.SSL_shutdown(sslSession, INVALID_FD, null);
                     fail();
                 } catch (NullPointerException expected) {
+                    // Expected.
                 }
             }
         });
@@ -2228,6 +2276,7 @@
                     NativeCrypto.SSL_shutdown(sslSession, INVALID_FD, DUMMY_CB);
                     fail();
                 } catch (SocketException expected) {
+                    // Expected.
                 }
             }
         });
@@ -2242,6 +2291,7 @@
             NativeCrypto.SSL_free(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         long c = NativeCrypto.SSL_CTX_new();
@@ -2258,6 +2308,7 @@
             NativeCrypto.SSL_SESSION_session_id(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         final ServerSocket listener = new ServerSocket(0);
@@ -2285,6 +2336,7 @@
             NativeCrypto.SSL_SESSION_get_time(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         final ServerSocket listener = new ServerSocket(0);
@@ -2314,6 +2366,7 @@
             NativeCrypto.SSL_SESSION_get_version(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         final ServerSocket listener = new ServerSocket(0);
@@ -2340,6 +2393,7 @@
             NativeCrypto.SSL_SESSION_cipher(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         final ServerSocket listener = new ServerSocket(0);
@@ -2375,6 +2429,7 @@
             NativeCrypto.i2d_SSL_SESSION(NULL);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         final ServerSocket listener = new ServerSocket(0);
@@ -2446,11 +2501,11 @@
 
     @Test
     public void test_RAND_bytes_Null_Failure() throws Exception {
-        byte[] output = null;
         try {
-            NativeCrypto.RAND_bytes(output);
+            NativeCrypto.RAND_bytes(null);
             fail("Should be an error on null buffer input");
         } catch (RuntimeException expected) {
+            // Expected.
         }
     }
 
@@ -2468,6 +2523,7 @@
             NativeCrypto.EVP_get_digestbyname("foobar");
             fail();
         } catch (RuntimeException expected) {
+            // Expected.
         }
     }
 
@@ -2496,12 +2552,14 @@
             NativeCrypto.EVP_DigestSignInit(ctx, 0, pkey);
             fail();
         } catch (RuntimeException expected) {
+            // Expected.
         }
 
         try {
             NativeCrypto.EVP_DigestSignInit(ctx, evpMd, null);
             fail();
         } catch (RuntimeException expected) {
+            // Expected.
         }
     }
 
@@ -2521,6 +2579,7 @@
             NativeCrypto.get_RSA_private_params(ctx);
             fail();
         } catch (RuntimeException expected) {
+            // Expected.
         }
     }
 
@@ -2540,6 +2599,7 @@
             NativeCrypto.get_RSA_public_params(ctx);
             fail();
         } catch (RuntimeException expected) {
+            // Expected.
         }
     }
 
@@ -2685,6 +2745,7 @@
             NativeCrypto.ECDH_compute_key(out, outOffset, null, pkey2Ref);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         // Assert that it fails when only the second key is null
@@ -2692,6 +2753,7 @@
             NativeCrypto.ECDH_compute_key(out, outOffset, pkey1Ref, null);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
     }
 
@@ -2705,6 +2767,7 @@
             NativeCrypto.EVP_CipherInit_ex(null, evpCipher, null, null, true);
             fail("Null context should throw NullPointerException");
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         /* Initialize encrypting. */
@@ -2750,7 +2813,7 @@
 
     @Test
     public void test_create_BIO_InputStream() throws Exception {
-        byte[] actual = "Test".getBytes();
+        byte[] actual = "Test".getBytes("UTF-8");
         ByteArrayInputStream is = new ByteArrayInputStream(actual);
 
         @SuppressWarnings("resource")
@@ -2768,7 +2831,7 @@
 
     @Test
     public void test_create_BIO_OutputStream() throws Exception {
-        byte[] actual = "Test".getBytes();
+        byte[] actual = "Test".getBytes("UTF-8");
         ByteArrayOutputStream os = new ByteArrayOutputStream();
 
         long ctx = NativeCrypto.create_BIO_OutputStream(os);
diff --git a/platform/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java b/platform/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java
index b7b7987..fc893d0 100644
--- a/platform/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java
+++ b/platform/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java
@@ -90,6 +90,7 @@
                 openTestFile("ct-server-key-public.pem")).getPublicKey();
         final CTLogInfo log = new CTLogInfo(key, "Test Log", "foo");
         CTLogStore store = new CTLogStore() {
+            @Override
             public CTLogInfo getKnownLog(byte[] logId) {
                 if (Arrays.equals(logId, log.getID())) {
                     return log;
@@ -121,12 +122,12 @@
             isHandshakeCompleted = true;
         }
 
-        protected SSLParametersImpl getContextSSLParameters(OpenSSLContextImpl context)
+        SSLParametersImpl getContextSSLParameters(OpenSSLContextImpl context)
                 throws IllegalAccessException {
             return (SSLParametersImpl) contextSSLParameters.get(context);
         }
 
-        protected TrustManagerImpl getSSLParametersTrustManager(SSLParametersImpl params)
+        TrustManagerImpl getSSLParametersTrustManager(SSLParametersImpl params)
                 throws IllegalAccessException {
             return (TrustManagerImpl) sslParametersTrustManager.get(params);
         }
@@ -202,11 +203,11 @@
         Exception clientException;
         Exception serverException;
 
-        public TestConnection(X509Certificate[] chain, PrivateKey key) throws Exception {
+        TestConnection(X509Certificate[] chain, PrivateKey key) throws Exception {
             this(chain, key, false);
         }
 
-        public TestConnection(X509Certificate[] chain, PrivateKey key, boolean useTrustManagerImpl)
+        TestConnection(X509Certificate[] chain, PrivateKey key, boolean useTrustManagerImpl)
                 throws Exception {
             clientHooks = new ClientHooks();
             serverHooks = new ServerHooks();
@@ -254,7 +255,7 @@
             }
         }
 
-        public void doHandshake() throws Exception {
+        void doHandshake() throws Exception {
             ServerSocket listener = new ServerSocket(0);
             Future<OpenSSLSocketImpl> clientFuture = handshake(listener, clientHooks);
             Future<OpenSSLSocketImpl> serverFuture = handshake(listener, serverHooks);
diff --git a/platform/src/test/java/org/conscrypt/TrustManagerImplTest.java b/platform/src/test/java/org/conscrypt/TrustManagerImplTest.java
index 52d35fd..f5a91c2 100644
--- a/platform/src/test/java/org/conscrypt/TrustManagerImplTest.java
+++ b/platform/src/test/java/org/conscrypt/TrustManagerImplTest.java
@@ -126,18 +126,20 @@
             tm.checkClientTrusted(chain, "RSA");
             fail();
         } catch (CertificateException expected) {
+            // Expected.
         }
         try {
             tm.checkServerTrusted(chain, "RSA");
             fail();
         } catch (CertificateException expected) {
+            // Expected.
         }
     }
 
-    private class MySSLSession implements SSLSession {
+    private static class MySSLSession implements SSLSession {
         private final String hostname;
 
-        public MySSLSession(String hostname) {
+        MySSLSession(String hostname) {
             this.hostname = hostname;
         }
 
diff --git a/platform/src/test/java/org/conscrypt/ct/CTLogStoreImplTest.java b/platform/src/test/java/org/conscrypt/ct/CTLogStoreImplTest.java
index 5c860c8..930b8a7 100644
--- a/platform/src/test/java/org/conscrypt/ct/CTLogStoreImplTest.java
+++ b/platform/src/test/java/org/conscrypt/ct/CTLogStoreImplTest.java
@@ -16,9 +16,14 @@
 
 package org.conscrypt.ct;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.StringBufferInputStream;
 import java.security.PublicKey;
@@ -172,7 +177,9 @@
     }
 
     private static void writeFile(File file, String contents) throws FileNotFoundException {
-        PrintWriter writer = new PrintWriter(file);
+        PrintWriter writer = new PrintWriter(
+                new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), UTF_8)),
+                false);
         try {
             writer.write(contents);
         } finally {
diff --git a/platform/src/test/java/org/conscrypt/ct/CTVerifierTest.java b/platform/src/test/java/org/conscrypt/ct/CTVerifierTest.java
index 1053afb..8be9dbc 100644
--- a/platform/src/test/java/org/conscrypt/ct/CTVerifierTest.java
+++ b/platform/src/test/java/org/conscrypt/ct/CTVerifierTest.java
@@ -33,6 +33,7 @@
 
     @Override
     public void setUp() throws Exception {
+        super.setUp();
         ca = OpenSSLX509Certificate.fromX509PemInputStream(openTestFile("ca-cert.pem"));
         cert = OpenSSLX509Certificate.fromX509PemInputStream(openTestFile("cert.pem"));
         certEmbedded = OpenSSLX509Certificate.fromX509PemInputStream(
@@ -43,6 +44,7 @@
 
         final CTLogInfo log = new CTLogInfo(key, "Test Log", "foo");
         CTLogStore store = new CTLogStore() {
+            @Override
             public CTLogInfo getKnownLog(byte[] logId) {
                 if (Arrays.equals(logId, log.getID())) {
                     return log;
diff --git a/settings.gradle b/settings.gradle
index 3478dbb..e24ab2b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -6,6 +6,8 @@
 include ":conscrypt-openjdk-uber"
 include ":conscrypt-android"
 include ":conscrypt-android-stub"
+include ":conscrypt-android-platform"
+include ":conscrypt-libcore-stub"
 include ":conscrypt-api-doclet"
 
 project(':conscrypt-constants').projectDir = "$rootDir/constants" as File
@@ -15,4 +17,6 @@
 project(':conscrypt-openjdk-uber').projectDir = "$rootDir/openjdk-uber" as File
 project(':conscrypt-android').projectDir = "$rootDir/android" as File
 project(':conscrypt-android-stub').projectDir = "$rootDir/android-stub" as File
+project(':conscrypt-libcore-stub').projectDir = "$rootDir/libcore-stub" as File
+project(':conscrypt-android-platform').projectDir = "$rootDir/platform" as File
 project(':conscrypt-api-doclet').projectDir = "$rootDir/api-doclet" as File
diff --git a/testing/build.gradle b/testing/build.gradle
index 90a1700..a4fd599 100644
--- a/testing/build.gradle
+++ b/testing/build.gradle
@@ -12,9 +12,10 @@
     // Only compile against this. Other modules will embed the generated code directly.
     compileOnly project(':conscrypt-constants')
 
-    compile libraries.bouncycastle_provider,
-            libraries.bouncycastle_apis,
-            libraries.guava,
-            libraries.junit,
+    compile project(':conscrypt-libcore-stub'),
             libraries.netty_handler
 }
+
+// Don't include this artifact in the distribution.
+tasks.install.enabled = false
+tasks.uploadArchives.enabled = false;
\ No newline at end of file
diff --git a/testing/src/main/java/org/conscrypt/testing/TestClient.java b/testing/src/main/java/org/conscrypt/TestClient.java
similarity index 98%
rename from testing/src/main/java/org/conscrypt/testing/TestClient.java
rename to testing/src/main/java/org/conscrypt/TestClient.java
index 15bd89b..6ef37f9 100644
--- a/testing/src/main/java/org/conscrypt/testing/TestClient.java
+++ b/testing/src/main/java/org/conscrypt/TestClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.conscrypt.testing;
+package org.conscrypt;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/testing/src/main/java/org/conscrypt/testing/TestServer.java b/testing/src/main/java/org/conscrypt/TestServer.java
similarity index 99%
rename from testing/src/main/java/org/conscrypt/testing/TestServer.java
rename to testing/src/main/java/org/conscrypt/TestServer.java
index f4b61cf..c706ba9 100644
--- a/testing/src/main/java/org/conscrypt/testing/TestServer.java
+++ b/testing/src/main/java/org/conscrypt/TestServer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.conscrypt.testing;
+package org.conscrypt;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/testing/src/main/java/org/conscrypt/testing/TestUtil.java b/testing/src/main/java/org/conscrypt/TestUtils.java
similarity index 92%
rename from testing/src/main/java/org/conscrypt/testing/TestUtil.java
rename to testing/src/main/java/org/conscrypt/TestUtils.java
index 40ae85e..4ea21de 100644
--- a/testing/src/main/java/org/conscrypt/testing/TestUtil.java
+++ b/testing/src/main/java/org/conscrypt/TestUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-package org.conscrypt.testing;
+package org.conscrypt;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.lang.reflect.Method;
 import java.net.ServerSocket;
 import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
 import java.security.NoSuchAlgorithmException;
 import java.security.Provider;
 import java.security.Security;
@@ -33,16 +36,19 @@
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SSLSocketFactory;
+import libcore.io.Streams;
 import libcore.java.security.TestKeyStore;
 
 /**
  * Utility methods to support testing.
  */
-public final class TestUtil {
+public final class TestUtils {
+    public static final Charset UTF_8 = Charset.forName("UTF-8");
+
     private static final Provider JDK_PROVIDER = getDefaultTlsProvider();
     private static final Provider CONSCRYPT_PROVIDER = getConscryptProvider();
     private static final byte[] CHARS =
-            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".getBytes();
+            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".getBytes(UTF_8);
     private static final Pattern KEY_PATTERN =
             Pattern.compile("-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header
                             "([a-z0-9+/=\\r\\n]+)" + // Base64 text
@@ -53,7 +59,19 @@
     public static final String PROVIDER_PROPERTY = "SSLContext.TLSv1.2";
     public static final String LOCALHOST = "localhost";
 
-    private TestUtil() {}
+    private TestUtils() {}
+
+    public static InputStream openTestFile(String name) throws FileNotFoundException {
+        InputStream is = TestUtils.class.getResourceAsStream("/" + name);
+        if (is == null) {
+            throw new FileNotFoundException(name);
+        }
+        return is;
+    }
+
+    public static byte[] readTestFile(String name) throws IOException {
+        return Streams.readFully(openTestFile(name));
+    }
 
     /**
      * Returns an array containing only {@link #PROTOCOL_TLS_V1_2}.
@@ -291,7 +309,7 @@
         throw new RuntimeException("Unable to find a default provider for " + PROVIDER_PROPERTY);
     }
 
-    private static final Provider getConscryptProvider() {
+    private static Provider getConscryptProvider() {
         try {
             return (Provider) Class.forName("org.conscrypt.OpenSSLProvider")
                     .getConstructor()