am 89b456da: Merge "Fix failing socket tests."

* commit '89b456da4190576d2c89ad01bd5a7627b0676c2c':
  Fix failing socket tests.
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index c857390..57a8f82 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -84,9 +84,6 @@
 LOCAL_MODULE := core-libart
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
 LOCAL_REQUIRED_MODULES := tzdata
-# Should not be dex-preopted as it isn't really a Dalvik boot jar or a
-# regular java library, but part of the image for ART.
-LOCAL_DEX_PREOPT := false
 include $(BUILD_JAVA_LIBRARY)
 
 ifeq ($(LIBCORE_SKIP_TESTS),)
@@ -165,9 +162,6 @@
 LOCAL_MODULE := core-libart-hostdex
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
 LOCAL_REQUIRED_MODULES := tzdata-host
-# Should not be dex-preopted as it isn't really a Dalvik boot jar or a
-# regular java library, but part of the image for ART.
-LOCAL_DEX_PREOPT := false
 include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
 
 # Make the core-tests library.
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index cf93bab..71f02ed 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -1461,6 +1461,7 @@
     "com.squareup.okhttp.internal.spdy.SpdyConnectionTest",
     "com.squareup.okhttp.internal.http.HttpOverHttp20Draft09Test",
     "com.squareup.okhttp.internal.http.HttpOverSpdy3Test",
+    "com.squareup.okhttp.internal.http.ResponseCacheAdapterTest",
     "com.squareup.okhttp.internal.http.URLConnectionTest#npnSetsProtocolHeader_SPDY_3",
     "com.squareup.okhttp.internal.http.URLConnectionTest#npnSetsProtocolHeader_HTTP_2",
     "com.squareup.okhttp.internal.http.URLConnectionTest#zeroLengthPost_SPDY_3",
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/net/MulticastSocketTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/net/MulticastSocketTest.java
index e3e1207..d531301 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/net/MulticastSocketTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/net/MulticastSocketTest.java
@@ -56,6 +56,7 @@
     private NetworkInterface loopbackInterface;
     private NetworkInterface ipv4NetworkInterface;
     private NetworkInterface ipv6NetworkInterface;
+    private boolean supportsMulticast;
 
     @Override
     protected void setUp() throws Exception {
@@ -69,6 +70,14 @@
         Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
         assertNotNull(interfaces);
 
+        // Determine if the device is marked to support multicast or not. If this propery is not
+        // set we assume the device has an interface capable of supporting multicast.
+        supportsMulticast = Boolean.valueOf(
+                System.getProperty("android.cts.device.multicast", "true"));
+        if (!supportsMulticast) {
+            return;
+        }
+
         while (interfaces.hasMoreElements()
                 && (ipv4NetworkInterface == null || ipv6NetworkInterface == null)) {
             NetworkInterface nextInterface = interfaces.nextElement();
@@ -90,6 +99,9 @@
     }
 
     public void test_Constructor() throws IOException {
+        if (!supportsMulticast) {
+            return;
+        }
         // Regression test for 497.
         MulticastSocket s = new MulticastSocket();
         // Regression test for Harmony-1162.
@@ -99,6 +111,9 @@
     }
 
     public void test_ConstructorI() throws IOException {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket orig = new MulticastSocket();
         int port = orig.getLocalPort();
         orig.close();
@@ -110,6 +125,9 @@
     }
 
     public void test_getInterface() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         // Validate that we get the expected response when one was not set.
         MulticastSocket mss = new MulticastSocket(0);
         // We expect an ANY address in this case.
@@ -134,6 +152,9 @@
     }
 
     public void test_getNetworkInterface() throws IOException {
+        if (!supportsMulticast) {
+            return;
+        }
         // Validate that we get the expected response when one was not set.
         MulticastSocket mss = new MulticastSocket(0);
         NetworkInterface theInterface = mss.getNetworkInterface();
@@ -176,6 +197,9 @@
     }
 
     public void test_getTimeToLive() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket();
         mss.setTimeToLive(120);
         assertEquals("Returned incorrect 1st TTL", 120, mss.getTimeToLive());
@@ -185,6 +209,9 @@
     }
 
     public void test_getTTL() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket();
         mss.setTTL((byte) 120);
         assertEquals("Returned incorrect TTL", 120, mss.getTTL());
@@ -192,10 +219,16 @@
     }
 
     public void test_joinGroupLjava_net_InetAddress_IPv4() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_joinGroupLjava_net_InetAddress(GOOD_IPv4);
     }
 
     public void test_joinGroupLjava_net_InetAddress_IPv6() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_joinGroupLjava_net_InetAddress(GOOD_IPv6);
     }
 
@@ -220,6 +253,9 @@
     }
 
     public void test_joinGroup_null_null() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket(0);
         try {
             mss.joinGroup(null, null);
@@ -230,6 +266,9 @@
     }
 
     public void test_joinGroup_non_multicast_address_IPv4() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket(0);
         try {
             mss.joinGroup(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0), null);
@@ -240,6 +279,9 @@
     }
 
     public void test_joinGroup_non_multicast_address_IPv6() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket(0);
         try {
             mss.joinGroup(new InetSocketAddress(InetAddress.getByName("::1"), 0), null);
@@ -251,12 +293,18 @@
 
     public void test_joinGroupLjava_net_SocketAddressLjava_net_NetworkInterface_IPv4()
             throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_joinGroupLjava_net_SocketAddressLjava_net_NetworkInterface(
                 ipv4NetworkInterface, GOOD_IPv4, BAD_IPv4);
     }
 
     public void test_joinGroupLjava_net_SocketAddressLjava_net_NetworkInterface_IPv6()
             throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_joinGroupLjava_net_SocketAddressLjava_net_NetworkInterface(
                 ipv6NetworkInterface, GOOD_IPv6, BAD_IPv6);
     }
@@ -310,6 +358,9 @@
     }
 
     public void test_joinGroupLjava_net_SocketAddressLjava_net_NetworkInterface() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         // Check that we can join on specific interfaces and that we only receive if data is
         // received on that interface. This test is only really useful on devices with multiple
         // non-loopback interfaces.
@@ -378,12 +429,18 @@
 
     public void test_joinGroupLjava_net_SocketAddressLjava_net_NetworkInterface_multiple_joins_IPv4()
             throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_joinGroupLjava_net_SocketAddressLjava_net_NetworkInterface_multiple_joins(
                 ipv4NetworkInterface, GOOD_IPv4);
     }
 
     public void test_joinGroupLjava_net_SocketAddressLjava_net_NetworkInterface_multiple_joins_IPv6()
             throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_joinGroupLjava_net_SocketAddressLjava_net_NetworkInterface_multiple_joins(
                 ipv6NetworkInterface, GOOD_IPv6);
     }
@@ -405,10 +462,16 @@
     }
 
     public void test_leaveGroupLjava_net_InetAddress_IPv4() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_leaveGroupLjava_net_InetAddress(GOOD_IPv4);
     }
 
     public void test_leaveGroupLjava_net_InetAddress_IPv6() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_leaveGroupLjava_net_InetAddress(GOOD_IPv6);
     }
 
@@ -428,6 +491,9 @@
     }
 
     public void test_leaveGroup_null_null() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket(0);
         try {
             mss.leaveGroup(null, null);
@@ -438,6 +504,9 @@
     }
 
     public void test_leaveGroup_non_multicast_address_IPv4() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket(0);
         try {
             mss.leaveGroup(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0), null);
@@ -448,6 +517,9 @@
     }
 
     public void test_leaveGroup_non_multicast_address_IPv6() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket(0);
         try {
             mss.leaveGroup(new InetSocketAddress(InetAddress.getByName("::1"), 0), null);
@@ -459,12 +531,18 @@
 
     public void test_leaveGroupLjava_net_SocketAddressLjava_net_NetworkInterface_IPv4()
             throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_leaveGroupLjava_net_SocketAddressLjava_net_NetworkInterface(
                 ipv4NetworkInterface, GOOD_IPv4, BAD_IPv4);
     }
 
     public void test_leaveGroupLjava_net_SocketAddressLjava_net_NetworkInterface_IPv6()
             throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_leaveGroupLjava_net_SocketAddressLjava_net_NetworkInterface(
                 ipv6NetworkInterface, GOOD_IPv6, BAD_IPv6);
     }
@@ -505,10 +583,16 @@
     }
 
     public void test_sendLjava_net_DatagramPacketB_IPv4() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_sendLjava_net_DatagramPacketB(GOOD_IPv4);
     }
 
     public void test_sendLjava_net_DatagramPacketB_IPv6() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_sendLjava_net_DatagramPacketB(GOOD_IPv6);
     }
 
@@ -531,6 +615,9 @@
     }
 
     public void test_setInterfaceLjava_net_InetAddress() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket();
         mss.setInterface(InetAddress.getLocalHost());
         InetAddress theInterface = mss.getInterface();
@@ -549,10 +636,16 @@
     }
 
     public void test_setInterface_unbound_address_IPv4() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_setInterface_unbound_address(GOOD_IPv4);
     }
 
     public void test_setInterface_unbound_address_IPv6() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_setInterface_unbound_address(GOOD_IPv6);
     }
 
@@ -568,6 +661,9 @@
     }
 
     public void test_setNetworkInterfaceLjava_net_NetworkInterface_null() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         // Validate that null interface is handled ok.
         MulticastSocket mss = new MulticastSocket();
         try {
@@ -579,6 +675,9 @@
     }
 
     public void test_setNetworkInterfaceLjava_net_NetworkInterface_round_trip() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         // Validate that we can get and set the interface.
         MulticastSocket mss = new MulticastSocket();
         mss.setNetworkInterface(ipv4NetworkInterface);
@@ -588,10 +687,16 @@
     }
 
     public void test_setNetworkInterfaceLjava_net_NetworkInterface_IPv4() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_setNetworkInterfaceLjava_net_NetworkInterface(GOOD_IPv4);
     }
 
     public void test_setNetworkInterfaceLjava_net_NetworkInterface_IPv6() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_setNetworkInterfaceLjava_net_NetworkInterface(GOOD_IPv6);
     }
 
@@ -630,6 +735,9 @@
     }
 
     public void test_setTimeToLiveI() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket();
         mss.setTimeToLive(120);
         assertEquals("Returned incorrect 1st TTL", 120, mss.getTimeToLive());
@@ -639,6 +747,9 @@
     }
 
     public void test_setTTLB() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket mss = new MulticastSocket();
         mss.setTTL((byte) 120);
         assertEquals("Failed to set TTL", 120, mss.getTTL());
@@ -646,6 +757,9 @@
     }
 
     public void test_ConstructorLjava_net_SocketAddress() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket ms = new MulticastSocket((SocketAddress) null);
         assertTrue("should not be bound", !ms.isBound() && !ms.isClosed() && !ms.isConnected());
         ms.bind(null);
@@ -677,6 +791,9 @@
     }
 
     public void test_getLoopbackMode() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket ms = new MulticastSocket(null);
         assertTrue("should not be bound", !ms.isBound() && !ms.isClosed() && !ms.isConnected());
         ms.getLoopbackMode();
@@ -686,6 +803,9 @@
     }
 
     public void test_setLoopbackModeZ() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         MulticastSocket ms = new MulticastSocket();
         ms.setLoopbackMode(true);
         assertTrue("loopback should be true", ms.getLoopbackMode());
@@ -696,10 +816,16 @@
     }
 
     public void test_setLoopbackModeSendReceive_IPv4() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_setLoopbackModeSendReceive(GOOD_IPv4);
     }
 
     public void test_setLoopbackModeSendReceive_IPv6() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         test_setLoopbackModeSendReceive(GOOD_IPv6);
     }
 
@@ -726,6 +852,9 @@
     }
 
     public void test_setReuseAddressZ() throws Exception {
+        if (!supportsMulticast) {
+            return;
+        }
         // Test case were we to set ReuseAddress to false.
         MulticastSocket theSocket1 = new MulticastSocket(null);
         theSocket1.setReuseAddress(false);
@@ -797,5 +926,4 @@
     private static String extractMessage(DatagramPacket rdp) {
         return new String(rdp.getData(), 0, rdp.getLength());
     }
-
 }
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarFileTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarFileTest.java
index f55829d..0bc8920 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarFileTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/jar/JarFileTest.java
@@ -37,7 +37,14 @@
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Enumeration;
+import java.util.List;
 import java.util.Vector;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
@@ -97,6 +104,27 @@
 
     private final String emptyEntryJar = "EmptyEntries_signed.jar";
 
+    /*
+     * /usr/bin/openssl genrsa 2048 > root1.pem
+     * /usr/bin/openssl req -new -key root1.pem -out root1.csr -subj '/CN=root1'
+     * /usr/bin/openssl x509 -req -days 3650 -in root1.csr -signkey root1.pem -out root1.crt
+     * /usr/bin/openssl genrsa 2048 > root2.pem
+     * /usr/bin/openssl req -new -key root2.pem -out root2.csr -subj '/CN=root2'
+     * echo 4000 > root1.srl
+     * echo 8000 > root2.srl
+     * /usr/bin/openssl x509 -req -days 3650 -in root2.csr -CA root1.crt -CAkey root1.pem -out root2.crt
+     * /usr/bin/openssl x509 -req -days 3650 -in root1.csr -CA root2.crt -CAkey root2.pem -out root1.crt
+     * /usr/bin/openssl genrsa 2048 > signer.pem
+     * /usr/bin/openssl req -new -key signer.pem -out signer.csr -subj '/CN=signer'
+     * /usr/bin/openssl x509 -req -days 3650 -in signer.csr -CA root1.crt -CAkey root1.pem -out signer.crt
+     * /usr/bin/openssl pkcs12 -inkey signer.pem -in signer.crt -export -out signer.p12 -name signer -passout pass:certloop
+     * keytool -importkeystore -srckeystore signer.p12 -srcstoretype PKCS12 -destkeystore signer.jks -srcstorepass certloop -deststorepass certloop
+     * cat signer.crt root1.crt root2.crt > chain.crt
+     * zip -d hyts_certLoop.jar 'META-INF/*'
+     * jarsigner -keystore signer.jks -certchain chain.crt -storepass certloop hyts_certLoop.jar signer
+     */
+    private final String certLoopJar = "hyts_certLoop.jar";
+
     private final String emptyEntry1 = "subfolder/internalSubset01.js";
 
     private final String emptyEntry2 = "svgtest.js";
@@ -616,6 +644,9 @@
 
         // JAR with a signature that has PKCS#7 Authenticated Attributes
         checkSignedJar(authAttrsJar);
+
+        // JAR with certificates that loop
+        checkSignedJar(certLoopJar, 3);
     }
 
     /**
@@ -628,29 +659,52 @@
         checkSignedJar(jarName9);
     }
 
+    /**
+     * Checks that a JAR is signed correctly with a signature length of 1.
+     */
     private void checkSignedJar(String jarName) throws Exception {
+        checkSignedJar(jarName, 1);
+    }
+
+    /**
+     * Checks that a JAR is signed correctly with a signature length of sigLength.
+     */
+    private void checkSignedJar(String jarName, final int sigLength) throws Exception {
         Support_Resources.copyFile(resources, null, jarName);
 
-        File file = new File(resources, jarName);
-        boolean foundCerts = false;
+        final File file = new File(resources, jarName);
 
-        JarFile jarFile = new JarFile(file, true);
-        try {
-
-            Enumeration<JarEntry> e = jarFile.entries();
-            while (e.hasMoreElements()) {
-                JarEntry entry = e.nextElement();
-                InputStream is = jarFile.getInputStream(entry);
-                is.skip(100000);
-                is.close();
-                Certificate[] certs = entry.getCertificates();
-                if (certs != null && certs.length > 0) {
-                    foundCerts = true;
-                    break;
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        Future<Boolean> future = executor.submit(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                JarFile jarFile = new JarFile(file, true);
+                try {
+                    Enumeration<JarEntry> e = jarFile.entries();
+                    while (e.hasMoreElements()) {
+                        JarEntry entry = e.nextElement();
+                        InputStream is = jarFile.getInputStream(entry);
+                        is.skip(100000);
+                        is.close();
+                        Certificate[] certs = entry.getCertificates();
+                        if (certs != null && certs.length > 0) {
+                            assertEquals(sigLength, certs.length);
+                            return true;
+                        }
+                    }
+                    return false;
+                } finally {
+                    jarFile.close();
                 }
             }
-        } finally {
-            jarFile.close();
+        });
+        executor.shutdown();
+        final boolean foundCerts;
+        try {
+            foundCerts = future.get(10, TimeUnit.SECONDS);
+        } catch (TimeoutException e) {
+            fail("Could not finish building chain; possibly confused by loops");
+            return; // Not actually reached.
         }
 
         assertTrue(
diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java
index e509a6e..a6368e8 100644
--- a/luni/src/main/java/java/util/Locale.java
+++ b/luni/src/main/java/java/util/Locale.java
@@ -95,7 +95,7 @@
  *     <td><a href="http://site.icu-project.org/download/51">ICU 51</a></td>
  *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-23">CLDR 23</a></td>
  *     <td><a href="http://www.unicode.org/versions/Unicode6.2.0/">Unicode 6.2</a></td></tr>
- * <tr><td>Android 4.? (STOPSHIP)</td>
+ * <tr><td>Android 5.0 (Lollipop)</td>
  *     <td><a href="http://site.icu-project.org/download/53">ICU 53</a></td>
  *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-25">CLDR 25</a></td>
  *     <td><a href="http://www.unicode.org/versions/Unicode6.3.0/">Unicode 6.3</a></td></tr>
diff --git a/luni/src/main/java/libcore/util/ZoneInfo.java b/luni/src/main/java/libcore/util/ZoneInfo.java
index 4a70a83..4d58d93 100644
--- a/luni/src/main/java/libcore/util/ZoneInfo.java
+++ b/luni/src/main/java/libcore/util/ZoneInfo.java
@@ -360,10 +360,16 @@
         private int gmtOffsetSeconds;
 
         public WallTime() {
-            this.calendar = new GregorianCalendar(false);
+            this.calendar = createGregorianCalendar();
             calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
         }
 
+        // LayoutLib replaces this method via bytecode manipulation, since the
+        // minimum-cost constructor is not available on host machines.
+        private static GregorianCalendar createGregorianCalendar() {
+            return new GregorianCalendar(false);
+        }
+
         /**
          * Sets the wall time to a point in time using the time zone information provided. This
          * is a replacement for the old native localtime_tz() function.
diff --git a/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java b/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
index 33584d8..020663e 100644
--- a/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
+++ b/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
@@ -31,6 +31,7 @@
 import java.security.Principal;
 import java.security.Signature;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
@@ -82,8 +83,10 @@
         CertificateFactory cf = CertificateFactory.getInstance("X.509");
         int i = 0;
         for (org.apache.harmony.security.x509.Certificate encCert : encCerts) {
-            final InputStream is = new ByteArrayInputStream(encCert.getEncoded());
-            certs[i++] = (X509Certificate) cf.generateCertificate(is);
+            final byte[] encoded = encCert.getEncoded();
+            final InputStream is = new ByteArrayInputStream(encoded);
+            certs[i++] = new VerbatimX509Certificate((X509Certificate) cf.generateCertificate(is),
+                    encoded);
         }
 
         List<SignerInfo> sigInfos = signedData.getSignerInfos();
@@ -246,6 +249,10 @@
             }
             chain.add(issuerCert);
             count++;
+            /* Prevent growing infinitely if there is a loop */
+            if (count > candidates.length) {
+                break;
+            }
             issuer = issuerCert.getIssuerDN();
             if (issuerCert.getSubjectDN().equals(issuer)) {
                 break;
@@ -263,4 +270,22 @@
         return null;
     }
 
+    /**
+     * For legacy reasons we need to return exactly the original encoded
+     * certificate bytes, instead of letting the underlying implementation have
+     * a shot at re-encoding the data.
+     */
+    private static class VerbatimX509Certificate extends WrappedX509Certificate {
+        private byte[] encodedVerbatim;
+
+        public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
+            super(wrapped);
+            this.encodedVerbatim = encodedVerbatim;
+        }
+
+        @Override
+        public byte[] getEncoded() throws CertificateEncodingException {
+            return encodedVerbatim;
+        }
+    }
 }
diff --git a/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java b/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java
new file mode 100644
index 0000000..2b09309
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2014 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.apache.harmony.security.utils;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+public class WrappedX509Certificate extends X509Certificate {
+    private final X509Certificate wrapped;
+
+    public WrappedX509Certificate(X509Certificate wrapped) {
+        this.wrapped = wrapped;
+    }
+
+    @Override
+    public Set<String> getCriticalExtensionOIDs() {
+        return wrapped.getCriticalExtensionOIDs();
+    }
+
+    @Override
+    public byte[] getExtensionValue(String oid) {
+        return wrapped.getExtensionValue(oid);
+    }
+
+    @Override
+    public Set<String> getNonCriticalExtensionOIDs() {
+        return wrapped.getNonCriticalExtensionOIDs();
+    }
+
+    @Override
+    public boolean hasUnsupportedCriticalExtension() {
+        return wrapped.hasUnsupportedCriticalExtension();
+    }
+
+    @Override
+    public void checkValidity() throws CertificateExpiredException,
+            CertificateNotYetValidException {
+        wrapped.checkValidity();
+    }
+
+    @Override
+    public void checkValidity(Date date) throws CertificateExpiredException,
+            CertificateNotYetValidException {
+        wrapped.checkValidity(date);
+    }
+
+    @Override
+    public int getVersion() {
+        return wrapped.getVersion();
+    }
+
+    @Override
+    public BigInteger getSerialNumber() {
+        return wrapped.getSerialNumber();
+    }
+
+    @Override
+    public Principal getIssuerDN() {
+        return wrapped.getIssuerDN();
+    }
+
+    @Override
+    public Principal getSubjectDN() {
+        return wrapped.getSubjectDN();
+    }
+
+    @Override
+    public Date getNotBefore() {
+        return wrapped.getNotBefore();
+    }
+
+    @Override
+    public Date getNotAfter() {
+        return wrapped.getNotAfter();
+    }
+
+    @Override
+    public byte[] getTBSCertificate() throws CertificateEncodingException {
+        return wrapped.getTBSCertificate();
+    }
+
+    @Override
+    public byte[] getSignature() {
+        return wrapped.getSignature();
+    }
+
+    @Override
+    public String getSigAlgName() {
+        return wrapped.getSigAlgName();
+    }
+
+    @Override
+    public String getSigAlgOID() {
+        return wrapped.getSigAlgOID();
+    }
+
+    @Override
+    public byte[] getSigAlgParams() {
+        return wrapped.getSigAlgParams();
+    }
+
+    @Override
+    public boolean[] getIssuerUniqueID() {
+        return wrapped.getIssuerUniqueID();
+    }
+
+    @Override
+    public boolean[] getSubjectUniqueID() {
+        return wrapped.getSubjectUniqueID();
+    }
+
+    @Override
+    public boolean[] getKeyUsage() {
+        return wrapped.getKeyUsage();
+    }
+
+    @Override
+    public int getBasicConstraints() {
+        return wrapped.getBasicConstraints();
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return wrapped.getEncoded();
+    }
+
+    @Override
+    public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
+        wrapped.verify(key);
+    }
+
+    @Override
+    public void verify(PublicKey key, String sigProvider) throws CertificateException,
+            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+            SignatureException {
+        verify(key, sigProvider);
+    }
+
+    @Override
+    public String toString() {
+        return wrapped.toString();
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        return wrapped.getPublicKey();
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java b/luni/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
index d1079c8..040a012 100644
--- a/luni/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
@@ -416,11 +416,13 @@
 
     private String resolveCharacterReference(String value, int base) {
         try {
-            int ch = Integer.parseInt(value, base);
-            if (ch < 0 || ch > Character.MAX_VALUE) {
-                return null;
+            int codePoint = Integer.parseInt(value, base);
+            if (Character.isBmpCodePoint(codePoint)) {
+                return String.valueOf((char) codePoint);
+            } else {
+                char[] surrogatePair = Character.toChars(codePoint);
+                return new String(surrogatePair);
             }
-            return String.valueOf((char) ch);
         } catch (NumberFormatException ex) {
             return null;
         }
diff --git a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
index bac8138..1c1296b 100644
--- a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
+++ b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
@@ -408,4 +408,16 @@
     assertEquals("یکشنبه د ۱۹۸۰ د فبروري ۱۰", formatDateRange(new Locale("ps"), utc, thisYear, thisYear, flags));
     assertEquals("วันอาทิตย์ 10 กุมภาพันธ์ 1980", formatDateRange(new Locale("th"), utc, thisYear, thisYear, flags));
   }
+
+  // http://b/13234532
+  public void test13234532() throws Exception {
+    Locale l = Locale.US;
+    TimeZone utc = TimeZone.getTimeZone("UTC");
+
+    int flags = FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL | FORMAT_12HOUR;
+
+    assertEquals("10 – 11 AM", formatDateRange(l, utc, 10*HOUR, 11*HOUR, flags));
+    assertEquals("11 AM – 1 PM", formatDateRange(l, utc, 11*HOUR, 13*HOUR, flags));
+    assertEquals("2 – 3 PM", formatDateRange(l, utc, 14*HOUR, 15*HOUR, flags));
+  }
 }
diff --git a/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java b/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java
index 1a24667..07ecd12 100644
--- a/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java
+++ b/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java
@@ -198,6 +198,11 @@
         assertFalse(verifyWithDomainNamePattern("ddd.", "d*d."));
     }
 
+    public void testNoPrefixMatch() {
+        assertFalse(verifyWithDomainNamePattern("imap.google.com.au", "imap.google.com"));
+        assertFalse(verifyWithDomainNamePattern("imap.google.com.au", "*.google.com"));
+    }
+
     public void testVerifyHostName() {
         assertTrue(verifyWithDomainNamePattern("a.b.c.d", "a.b.c.d"));
         assertTrue(verifyWithDomainNamePattern("a.b.c.d", "*.b.c.d"));
diff --git a/luni/src/test/java/libcore/xml/KxmlSerializerTest.java b/luni/src/test/java/libcore/xml/KxmlSerializerTest.java
index 6a75a9b..5f68a99 100644
--- a/luni/src/test/java/libcore/xml/KxmlSerializerTest.java
+++ b/luni/src/test/java/libcore/xml/KxmlSerializerTest.java
@@ -22,7 +22,9 @@
 import junit.framework.TestCase;
 import org.kxml2.io.KXmlSerializer;
 import org.w3c.dom.Document;
+import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
 import org.xmlpull.v1.XmlSerializer;
 import static tests.support.Support_Xml.domOf;
 
@@ -87,12 +89,67 @@
         return serializer;
     }
 
+    public String fromCodePoint(int codePoint) {
+        if (codePoint > Character.MAX_VALUE) {
+            return new String(Character.toChars(codePoint));
+        }
+        return Character.toString((char) codePoint);
+    }
+
+    // http://b/17960630
+    public void testSpeakNoEvilMonkeys() throws Exception {
+        StringWriter stringWriter = new StringWriter();
+        XmlSerializer serializer = new KXmlSerializer();
+        serializer.setOutput(stringWriter);
+        serializer.startDocument("UTF-8", null);
+        serializer.startTag(NAMESPACE, "tag");
+        serializer.attribute(NAMESPACE, "attr", "a\ud83d\ude4ab");
+        serializer.text("c\ud83d\ude4ad");
+        serializer.cdsect("e\ud83d\ude4af");
+        serializer.endTag(NAMESPACE, "tag");
+        serializer.endDocument();
+        assertXmlEquals("<tag attr=\"a&#128586;b\">" +
+                        "c&#128586;d" +
+                        "<![CDATA[e]]>&#128586;<![CDATA[f]]>" +
+                        "</tag>", stringWriter.toString());
+
+        // Check we can parse what we just output.
+        Document doc = domOf(stringWriter.toString());
+        Node root = doc.getDocumentElement();
+        assertEquals("a\ud83d\ude4ab", root.getAttributes().getNamedItem("attr").getNodeValue());
+        Text text = (Text) root.getFirstChild();
+        assertEquals("c\ud83d\ude4ade\ud83d\ude4af", text.getNodeValue());
+    }
+
+    public void testBadSurrogates() throws Exception {
+        StringWriter stringWriter = new StringWriter();
+        XmlSerializer serializer = new KXmlSerializer();
+        serializer.setOutput(stringWriter);
+        serializer.startDocument("UTF-8", null);
+        serializer.startTag(NAMESPACE, "tag");
+        try {
+            serializer.attribute(NAMESPACE, "attr", "a\ud83d\u0040b");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            serializer.text("c\ud83d\u0040d");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            serializer.cdsect("e\ud83d\u0040f");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    // Cover all the BMP code points plus a few that require us to use surrogates.
+    private static int MAX_TEST_CODE_POINT = 0x10008;
+
     public void testInvalidCharactersInText() throws IOException {
         XmlSerializer serializer = newSerializer();
         serializer.startTag(NAMESPACE, "root");
-        for (int ch = 0; ch <= 0xffff; ++ch) {
-            final String s = Character.toString((char) ch);
-            if (isValidXmlCodePoint(ch)) {
+        for (int c = 0; c <= MAX_TEST_CODE_POINT; ++c) {
+            final String s = fromCodePoint(c);
+            if (isValidXmlCodePoint(c)) {
                 serializer.text("a" + s + "b");
             } else {
                 try {
@@ -108,9 +165,9 @@
     public void testInvalidCharactersInAttributeValues() throws IOException {
         XmlSerializer serializer = newSerializer();
         serializer.startTag(NAMESPACE, "root");
-        for (int ch = 0; ch <= 0xffff; ++ch) {
-            final String s = Character.toString((char) ch);
-            if (isValidXmlCodePoint(ch)) {
+        for (int c = 0; c <= MAX_TEST_CODE_POINT; ++c) {
+            final String s = fromCodePoint(c);
+            if (isValidXmlCodePoint(c)) {
                 serializer.attribute(NAMESPACE, "a", "a" + s + "b");
             } else {
                 try {
@@ -126,9 +183,9 @@
     public void testInvalidCharactersInCdataSections() throws IOException {
         XmlSerializer serializer = newSerializer();
         serializer.startTag(NAMESPACE, "root");
-        for (int ch = 0; ch <= 0xffff; ++ch) {
-            final String s = Character.toString((char) ch);
-            if (isValidXmlCodePoint(ch)) {
+        for (int c = 0; c <= MAX_TEST_CODE_POINT; ++c) {
+            final String s = fromCodePoint(c);
+            if (isValidXmlCodePoint(c)) {
                 serializer.cdsect("a" + s + "b");
             } else {
                 try {
diff --git a/support/src/test/java/libcore/java/security/TestKeyStore.java b/support/src/test/java/libcore/java/security/TestKeyStore.java
index 203c028..bd64360 100644
--- a/support/src/test/java/libcore/java/security/TestKeyStore.java
+++ b/support/src/test/java/libcore/java/security/TestKeyStore.java
@@ -47,6 +47,7 @@
 import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.Security;
+import java.security.UnrecoverableEntryException;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
@@ -260,6 +261,7 @@
         private X500Principal subject;
         private int keyUsage;
         private boolean ca;
+        private PrivateKeyEntry privateEntry;
         private PrivateKeyEntry signer;
         private Certificate rootCa;
         private final List<KeyPurposeId> extendedKeyUsages = new ArrayList<KeyPurposeId>();
@@ -314,6 +316,12 @@
             return this;
         }
 
+        /** a private key entry to use for the generation of the certificate */
+        public Builder privateEntry(PrivateKeyEntry privateEntry) {
+            this.privateEntry = privateEntry;
+            return this;
+        }
+
         /** a private key entry to be used for signing, otherwise self-sign */
         public Builder signer(PrivateKeyEntry signer) {
             this.signer = signer;
@@ -368,21 +376,32 @@
                     }
                 }
 
+                /*
+                 * This is not implemented for other key types because the logic
+                 * would be long to write and it's not needed currently.
+                 */
+                if (privateEntry != null
+                        && (keyAlgorithms.length != 1 || !"RSA".equals(keyAlgorithms[0]))) {
+                    throw new IllegalStateException(
+                            "Only reusing an existing key is implemented for RSA");
+                }
+
                 KeyStore keyStore = createKeyStore();
                 for (String keyAlgorithm : keyAlgorithms) {
                     String publicAlias  = aliasPrefix + "-public-"  + keyAlgorithm;
                     String privateAlias = aliasPrefix + "-private-" + keyAlgorithm;
                     if ((keyAlgorithm.equals("EC_RSA") || keyAlgorithm.equals("DH_RSA"))
                             && signer == null && rootCa == null) {
-                        createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias,
-                                   privateKey(keyStore, keyPassword, "RSA", "RSA"));
+                        createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias, null,
+                                privateKey(keyStore, keyPassword, "RSA", "RSA"));
                         continue;
                     } else if (keyAlgorithm.equals("DH_DSA") && signer == null && rootCa == null) {
-                        createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias,
+                        createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias, null,
                                 privateKey(keyStore, keyPassword, "DSA", "DSA"));
                         continue;
                     }
-                    createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias, signer);
+                    createKeys(keyStore, keyAlgorithm, publicAlias, privateAlias, privateEntry,
+                            signer);
                 }
                 if (rootCa != null) {
                     keyStore.setCertificateEntry(aliasPrefix
@@ -416,6 +435,7 @@
                 String keyAlgorithm,
                 String publicAlias,
                 String privateAlias,
+                PrivateKeyEntry privateEntry,
                 PrivateKeyEntry signer) throws Exception {
             PrivateKey caKey;
             X509Certificate caCert;
@@ -430,42 +450,51 @@
                 caCertChain = (X509Certificate[])signer.getCertificateChain();
             }
 
-            PrivateKey privateKey;
+            final PrivateKey privateKey;
+            final PublicKey publicKey;
             X509Certificate x509c;
             if (publicAlias == null && privateAlias == null) {
                 // don't want anything apparently
                 privateKey = null;
+                publicKey = null;
                 x509c = null;
             } else {
-                // 1.) we make the keys
-                int keySize;
-                if (keyAlgorithm.equals("RSA")) {
-                    // 512 breaks SSL_RSA_EXPORT_* on RI and TLS_ECDHE_RSA_WITH_RC4_128_SHA for us
-                    keySize =  1024;
-                } else if (keyAlgorithm.equals("DH_RSA")) {
-                    keySize = 512;
-                    keyAlgorithm = "DH";
-                } else if (keyAlgorithm.equals("DSA")) {
-                    keySize = 512;
-                } else if (keyAlgorithm.equals("DH_DSA")) {
-                    keySize = 512;
-                    keyAlgorithm = "DH";
-                } else if (keyAlgorithm.equals("EC")) {
-                    keySize = 256;
-                } else if (keyAlgorithm.equals("EC_RSA")) {
-                    keySize = 256;
-                    keyAlgorithm = "EC";
+                if (privateEntry == null) {
+                    // 1a.) we make the keys
+                    int keySize;
+                    if (keyAlgorithm.equals("RSA")) {
+                        // 512 breaks SSL_RSA_EXPORT_* on RI and
+                        // TLS_ECDHE_RSA_WITH_RC4_128_SHA for us
+                        keySize = 1024;
+                    } else if (keyAlgorithm.equals("DH_RSA")) {
+                        keySize = 512;
+                        keyAlgorithm = "DH";
+                    } else if (keyAlgorithm.equals("DSA")) {
+                        keySize = 512;
+                    } else if (keyAlgorithm.equals("DH_DSA")) {
+                        keySize = 512;
+                        keyAlgorithm = "DH";
+                    } else if (keyAlgorithm.equals("EC")) {
+                        keySize = 256;
+                    } else if (keyAlgorithm.equals("EC_RSA")) {
+                        keySize = 256;
+                        keyAlgorithm = "EC";
+                    } else {
+                        throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm);
+                    }
+
+                    KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyAlgorithm);
+                    kpg.initialize(keySize, new SecureRandom());
+
+                    KeyPair kp = kpg.generateKeyPair();
+                    privateKey = kp.getPrivate();
+                    publicKey = kp.getPublic();
                 } else {
-                    throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm);
+                    // 1b.) we use the previous keys
+                    privateKey = privateEntry.getPrivateKey();
+                    publicKey = privateEntry.getCertificate().getPublicKey();
                 }
 
-                KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyAlgorithm);
-                kpg.initialize(keySize, new SecureRandom());
-
-                KeyPair kp = kpg.generateKeyPair();
-                privateKey = kp.getPrivate();
-                PublicKey publicKey  = kp.getPublic();
-
                 // 2.) use keys to make certificate
                 X500Principal issuer = ((caCert != null)
                                         ? caCert.getSubjectX500Principal()
@@ -820,6 +849,24 @@
     }
 
     /**
+     * Return an {@code X509Certificate that matches the given {@code alias}.
+     */
+    public KeyStore.Entry getEntryByAlias(String alias) {
+        return entryByAlias(keyStore, alias);
+    }
+
+    /**
+     * Finds an entry in the keystore by the given alias.
+     */
+    public static KeyStore.Entry entryByAlias(KeyStore keyStore, String alias) {
+        try {
+            return keyStore.getEntry(alias, null);
+        } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
      * Create a client key store that only contains self-signed certificates but no private keys
      */
     public static KeyStore createClient(KeyStore caKeyStore) {
diff --git a/support/src/test/java/tests/resources/hyts_certLoop.jar b/support/src/test/java/tests/resources/hyts_certLoop.jar
new file mode 100644
index 0000000..cb4ebe1
--- /dev/null
+++ b/support/src/test/java/tests/resources/hyts_certLoop.jar
Binary files differ
diff --git a/xml/src/main/java/org/kxml2/io/KXmlSerializer.java b/xml/src/main/java/org/kxml2/io/KXmlSerializer.java
index 8fa2756..bfdeece 100644
--- a/xml/src/main/java/org/kxml2/io/KXmlSerializer.java
+++ b/xml/src/main/java/org/kxml2/io/KXmlSerializer.java
@@ -125,14 +125,18 @@
                     // otherwise generate.
                     // Note: tab, newline, and carriage return have already been
                     // handled above.
-                    boolean valid = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
-                    if (!valid) {
-                        reportInvalidCharacter(c);
-                    }
-                    if (unicode || c < 127) {
-                        writer.write(c);
+                    boolean allowedInXml = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
+                    if (allowedInXml) {
+                        if (unicode || c < 127) {
+                            writer.write(c);
+                        } else {
+                            writer.write("&#" + ((int) c) + ";");
+                        }
+                    } else if (Character.isHighSurrogate(c) && i < s.length() - 1) {
+                        writeSurrogate(c, s.charAt(i + 1));
+                        ++i;
                     } else {
-                        writer.write("&#" + ((int) c) + ";");
+                        reportInvalidCharacter(c);
                     }
                     // END android-changed
             }
@@ -141,7 +145,7 @@
 
     // BEGIN android-added
     private static void reportInvalidCharacter(char ch) {
-        throw new IllegalArgumentException("Illegal character (" + Integer.toHexString((int) ch) + ")");
+        throw new IllegalArgumentException("Illegal character (U+" + Integer.toHexString((int) ch) + ")");
     }
     // END android-added
 
@@ -548,22 +552,41 @@
         // BEGIN android-changed: ]]> is not allowed within a CDATA,
         // so break and start a new one when necessary.
         data = data.replace("]]>", "]]]]><![CDATA[>");
-        char[] chars = data.toCharArray();
-        // We also aren't allowed any invalid characters.
-        for (char ch : chars) {
-            boolean valid = (ch >= 0x20 && ch <= 0xd7ff) ||
+        writer.write("<![CDATA[");
+        for (int i = 0; i < data.length(); ++i) {
+            char ch = data.charAt(i);
+            boolean allowedInCdata = (ch >= 0x20 && ch <= 0xd7ff) ||
                     (ch == '\t' || ch == '\n' || ch == '\r') ||
                     (ch >= 0xe000 && ch <= 0xfffd);
-            if (!valid) {
+            if (allowedInCdata) {
+                writer.write(ch);
+            } else if (Character.isHighSurrogate(ch) && i < data.length() - 1) {
+                // Character entities aren't valid in CDATA, so break out for this.
+                writer.write("]]>");
+                writeSurrogate(ch, data.charAt(++i));
+                writer.write("<![CDATA[");
+            } else {
                 reportInvalidCharacter(ch);
             }
         }
-        writer.write("<![CDATA[");
-        writer.write(chars, 0, chars.length);
         writer.write("]]>");
         // END android-changed
     }
 
+    // BEGIN android-added
+    private void writeSurrogate(char high, char low) throws IOException {
+        if (!Character.isLowSurrogate(low)) {
+            throw new IllegalArgumentException("Bad surrogate pair (U+" + Integer.toHexString((int) high) +
+                                               " U+" + Integer.toHexString((int) low) + ")");
+        }
+        // Java-style surrogate pairs aren't allowed in XML. We could use the > 3-byte encodings, but that
+        // seems likely to upset anything expecting modified UTF-8 rather than "real" UTF-8. It seems more
+        // conservative in a Java environment to use an entity reference instead.
+        int codePoint = Character.toCodePoint(high, low);
+        writer.write("&#" + codePoint + ";");
+    }
+    // END android-added
+
     public void comment(String comment) throws IOException {
         check(false);
         writer.write("<!--");