Merge "Fix SimpleDateFormatTest when run outside California"
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index 698ebc5..01991b0 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -1374,6 +1374,11 @@
   ]
 },
 {
+  description: "ScannerParseLargeFileBenchmark can cause a failure due to a timeout",
+  bug: 14865710,
+  name: "org.apache.harmony.tests.java.util.ScannerParseLargeFileBenchmarkTest"
+},
+{
   description: "Known failure in GregorianCalendarTest",
   bug: 12778197,
   name: "org.apache.harmony.tests.java.util.GregorianCalendarTest#test_computeTime"
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/channels/ServerSocketChannelTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/channels/ServerSocketChannelTest.java
index c1d592a..be40d0b 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/channels/ServerSocketChannelTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/channels/ServerSocketChannelTest.java
@@ -657,20 +657,35 @@
      * @tests ServerSocketChannel#socket().getSoTimeout()
      */
     public void test_accept_SOTIMEOUT() throws IOException {
-        // regression test for Harmony-707
-        final int SO_TIMEOUT = 10;
+        // Regression test for Harmony-707
+        // The timeout actually used may be different from the one set due to
+        // rounding by the Linux Kernel (see sock_set_timeout() in net/core/sock.c).
+        // getSoTimeout() can return a different value from the one set with
+        // setSoTimeout(). Consequently we do not check for equality with what was
+        // set.
+
         ServerSocketChannel sc = ServerSocketChannel.open();
         try {
             sc.socket().bind(null);
+
+            // Non blocking mode, accept() will return NULL since there are no pending connections.
             sc.configureBlocking(false);
+
             ServerSocket ss = sc.socket();
+
+            int defaultTimeout = ss.getSoTimeout();
+            assertEquals(0, defaultTimeout);
+            // The timeout value is unimportant, providing it is large enough to be accepted
+            // by the Kernel as distinct from the default.
+            final int SO_TIMEOUT = 200;
             ss.setSoTimeout(SO_TIMEOUT);
+            int nonDefaultTimeout = ss.getSoTimeout();
+            assertTrue(nonDefaultTimeout != defaultTimeout);
+
             SocketChannel client = sc.accept();
-            // non blocking mode, returns null since there are no pending connections.
             assertNull(client);
-            int soTimeout = ss.getSoTimeout();
-            // Harmony fails here.
-            assertEquals(SO_TIMEOUT, soTimeout);
+            // Confirm the timeout was unchanged.
+            assertEquals(nonDefaultTimeout, ss.getSoTimeout());
         } finally {
             sc.close();
         }
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/DateFormatSymbolsTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/DateFormatSymbolsTest.java
index 70e41a2..1a6a25e 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/DateFormatSymbolsTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/DateFormatSymbolsTest.java
@@ -16,8 +16,6 @@
  */
 package org.apache.harmony.tests.java.text;
 
-import java.io.File;
-import java.net.URL;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
@@ -25,7 +23,6 @@
 import java.text.DateFormatSymbols;
 import java.util.Arrays;
 import java.util.Locale;
-import java.util.ServiceConfigurationError;
 
 public class DateFormatSymbolsTest extends junit.framework.TestCase {
 
@@ -402,13 +399,6 @@
         dfs = new DateFormatSymbols(new Locale("en", "us"));
     }
 
-    /**
-     * Tears down the fixture, for example, close a network connection. This
-     * method is called after a test is executed.
-     */
-    protected void tearDown() {
-    }
-
     // Test serialization mechanism of DateFormatSymbols
     public void test_serialization() throws Exception {
         DateFormatSymbols symbols = new DateFormatSymbols(Locale.FRANCE);
@@ -426,7 +416,6 @@
         DateFormatSymbols symbolsD = (DateFormatSymbols) objectIStream
                 .readObject();
 
-        // The associated currency will not persist
         String[][] zoneStringsD = symbolsD.getZoneStrings();
         assertNotNull(zoneStringsD);
         assertEquals(symbols, symbolsD);
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ScannerParseLargeFileBenchmarkTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ScannerParseLargeFileBenchmarkTest.java
index 4b0d1ea..c0f9e58 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ScannerParseLargeFileBenchmarkTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ScannerParseLargeFileBenchmarkTest.java
@@ -24,11 +24,11 @@
 public class ScannerParseLargeFileBenchmarkTest extends TestCase {
 
     /**
-     * This test will check when parse a large file like more than 200M bytes if
-     * the Scanner will exhaust all heap memory
+     * Check whether the Scanner will exhaust all heap memory when parsing a
+     * large file.
      */
     public void testParseLargeFile() throws Exception {
-        MyReader reader = new MyReader();
+        FakeLargeFile reader = new FakeLargeFile();
         String delimiter = "\r?\n";
         Scanner scanner = new Scanner(reader).useDelimiter(delimiter);
 
@@ -39,14 +39,9 @@
         reader.close();
     }
 
-    private static class MyReader extends Reader {
-        static final char[] CONTENT = "large file!\n".toCharArray();
-
-        static long fileLength = (8 << 21) * 12;
-
-        static boolean first = true;
-
-        static int position = 0;
+    private static class FakeLargeFile extends Reader {
+        private static final char[] CONTENT = "large file!\n".toCharArray();
+        private static final int FILE_LENGTH = 192 * 1024 * 1024; // 192 MB
 
         private int count = 0;
 
@@ -55,22 +50,24 @@
         }
 
         @Override
-        public int read(char[] buf, int offset, int length) {
-            if (count >= fileLength) {
+        public int read(char[] buffer, int offset, int length) {
+            if (count >= FILE_LENGTH) {
                 return -1;
             }
-            if (first == true) {
-                position = 0;
-                first = false;
-            }
-            for (int i = offset; i < length; i++) {
-                buf[i] = CONTENT[(i + position) % CONTENT.length];
-                count++;
-            }
 
-            position = (length + position) % CONTENT.length;
-
-            return length - offset;
+            final int charsToRead = Math.min(FILE_LENGTH - count, length);
+            int bufferIndex = offset;
+            int contentIndex = count % CONTENT.length;
+            int charsRead = 0;
+            while (charsRead < charsToRead) {
+                buffer[bufferIndex++] = CONTENT[contentIndex++];
+                if (contentIndex == CONTENT.length) {
+                    contentIndex = 0;
+                }
+                charsRead++;
+            }
+            count += charsRead;
+            return charsToRead;
         }
     }
 }
diff --git a/luni/src/main/java/java/lang/Character.java b/luni/src/main/java/java/lang/Character.java
index 8efd6cd..e920a3e 100644
--- a/luni/src/main/java/java/lang/Character.java
+++ b/luni/src/main/java/java/lang/Character.java
@@ -2530,25 +2530,28 @@
     }
 
     /**
-     * Gets the Unicode directionality of the specified character.
-     *
-     * @param codePoint
-     *            the Unicode code point to get the directionality of.
-     * @return the Unicode directionality of {@code codePoint}.
+     * Returns the Unicode directionality of the given code point.
+     * This will be one of the {@code DIRECTIONALITY_} constants.
+     * For characters whose directionality is undefined, or whose
+     * directionality has no appropriate constant in this class,
+     * {@code DIRECTIONALITY_UNDEFINED} is returned.
      */
     public static byte getDirectionality(int codePoint) {
         if (getType(codePoint) == Character.UNASSIGNED) {
             return Character.DIRECTIONALITY_UNDEFINED;
         }
 
-        byte directionality = getDirectionalityImpl(codePoint);
-        if (directionality == -1) {
-            return -1;
+        byte directionality = getIcuDirectionality(codePoint);
+        if (directionality >= 0 && directionality < DIRECTIONALITY.length) {
+            return DIRECTIONALITY[directionality];
         }
-        return DIRECTIONALITY[directionality];
+        return Character.DIRECTIONALITY_UNDEFINED;
     }
 
-    private static native byte getDirectionalityImpl(int codePoint);
+    /**
+     * @hide - internal use only.
+     */
+    public static native byte getIcuDirectionality(int codePoint);
 
     /**
      * Indicates whether the specified character is mirrored.
diff --git a/luni/src/main/java/java/text/DateFormatSymbols.java b/luni/src/main/java/java/text/DateFormatSymbols.java
index 54a1a3f..ee34bbd 100644
--- a/luni/src/main/java/java/text/DateFormatSymbols.java
+++ b/luni/src/main/java/java/text/DateFormatSymbols.java
@@ -62,16 +62,14 @@
     transient LocaleData localeData;
 
     // Localized display names.
-    String[][] zoneStrings;
-    // Has the user called setZoneStrings?
-    transient boolean customZoneStrings;
+    private String[][] zoneStrings;
 
-    /**
-     * Locale, necessary to lazily load time zone strings. We force the time
-     * zone names to load upon serialization, so this will never be needed
-     * post deserialization.
+    /*
+     * Locale, necessary to lazily load time zone strings. Added to the serialized form for
+     * Android's L release. May be null if the object was deserialized using data from older
+     * releases. See b/16502916.
      */
-    transient final Locale locale;
+    private final Locale locale;
 
     /**
      * Gets zone strings, initializing them if necessary. Does not create
@@ -153,11 +151,12 @@
     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
         ois.defaultReadObject();
 
-        // NOTE: We don't serialize the locale we were created with, so we can't
-        // get back the localeData object we want. This is broken for callers that
-        // access this field directly (i.e, SimpleDateFormat). We should ideally
-        // have serialized the locale we were created with. See b/16502916.
-        this.localeData = LocaleData.get(Locale.getDefault());
+        Locale locale = this.locale;
+        if (locale == null) {
+            // Before the L release Android did not serialize the locale. Handle its absence.
+            locale = Locale.getDefault();
+        }
+        this.localeData = LocaleData.get(locale);
     }
 
     private void writeObject(ObjectOutputStream oos) throws IOException {
@@ -220,7 +219,6 @@
         // 'zoneStrings' is so large, we just print a representative value.
         return getClass().getName() +
                 "[amPmStrings=" + Arrays.toString(ampms) +
-                ",customZoneStrings=" + customZoneStrings +
                 ",eras=" + Arrays.toString(eras) +
                 ",localPatternChars=" + localPatternChars +
                 ",months=" + Arrays.toString(months) +
@@ -488,6 +486,25 @@
             }
         }
         this.zoneStrings = clone2dStringArray(zoneStrings);
-        this.customZoneStrings = true;
+    }
+
+    /**
+     * Returns the display name of the timezone specified. Returns null if no name was found in the
+     * zone strings.
+     *
+     * @param daylight whether to return the daylight savings or the standard name
+     * @param style one of the {@link TimeZone} styles such as {@link TimeZone#SHORT}
+     *
+     * @hide used internally
+     */
+    String getTimeZoneDisplayName(TimeZone tz, boolean daylight, int style) {
+        if (style != TimeZone.SHORT && style != TimeZone.LONG) {
+            throw new IllegalArgumentException("Bad style: " + style);
+        }
+
+        // If custom zone strings have been set with setZoneStrings() we use those. Otherwise we
+        // use the ones from LocaleData.
+        String[][] zoneStrings = internalZoneStrings();
+        return TimeZoneNames.getDisplayName(zoneStrings, tz.getID(), daylight, style);
     }
 }
diff --git a/luni/src/main/java/java/text/SimpleDateFormat.java b/luni/src/main/java/java/text/SimpleDateFormat.java
index 259bfe0..c1a8ee9 100644
--- a/luni/src/main/java/java/text/SimpleDateFormat.java
+++ b/luni/src/main/java/java/text/SimpleDateFormat.java
@@ -740,15 +740,9 @@
             TimeZone tz = calendar.getTimeZone();
             boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
             int style = count < 4 ? TimeZone.SHORT : TimeZone.LONG;
-            if (!formatData.customZoneStrings) {
-                buffer.append(tz.getDisplayName(daylight, style, formatData.locale));
-                return;
-            }
-            // We can't call TimeZone.getDisplayName() because it would not use
-            // the custom DateFormatSymbols of this SimpleDateFormat.
-            String custom = TimeZoneNames.getDisplayName(formatData.zoneStrings, tz.getID(), daylight, style);
-            if (custom != null) {
-                buffer.append(custom);
+            String zoneString = formatData.getTimeZoneDisplayName(tz, daylight, style);
+            if (zoneString != null) {
+                buffer.append(zoneString);
                 return;
             }
         }
@@ -759,21 +753,11 @@
     // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts.
     // @param generalTimeZone "GMT-08:00" rather than "-0800".
     private void appendNumericTimeZone(StringBuffer buffer, int count, boolean generalTimeZone) {
-        int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
-        char sign = '+';
-        if (offset < 0) {
-            sign = '-';
-            offset = -offset;
-        }
-        if (generalTimeZone || count == 4) {
-            buffer.append("GMT");
-        }
-        buffer.append(sign);
-        appendNumber(buffer, 2, offset / 3600000);
-        if (generalTimeZone || count >= 4) {
-            buffer.append(':');
-        }
-        appendNumber(buffer, 2, (offset % 3600000) / 60000);
+        int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
+        boolean includeGmt = generalTimeZone || count == 4;
+        boolean includeMinuteSeparator = generalTimeZone || count >= 4;
+        buffer.append(TimeZone.createGmtOffsetString(includeGmt,  includeMinuteSeparator,
+                offsetMillis));
     }
 
     private void appendMilliseconds(StringBuffer buffer, int count, int value) {
diff --git a/luni/src/main/java/java/util/Scanner.java b/luni/src/main/java/java/util/Scanner.java
index 7d504b7..7d0e795 100644
--- a/luni/src/main/java/java/util/Scanner.java
+++ b/luni/src/main/java/java/util/Scanner.java
@@ -159,12 +159,15 @@
         if (charsetName == null) {
             throw new IllegalArgumentException("charsetName == null");
         }
+
+        InputStreamReader streamReader;
         try {
-            setInput(new InputStreamReader(fis, charsetName));
+            streamReader = new InputStreamReader(fis, charsetName);
         } catch (UnsupportedEncodingException e) {
             IoUtils.closeQuietly(fis);
             throw new IllegalArgumentException(e.getMessage());
         }
+        initialize(streamReader);
     }
 
     /**
@@ -174,7 +177,7 @@
      *            the string to be scanned.
      */
     public Scanner(String src) {
-        setInput(new StringReader(src));
+        initialize(new StringReader(src));
     }
 
     /**
@@ -203,11 +206,14 @@
         if (src == null) {
             throw new NullPointerException("src == null");
         }
+
+        InputStreamReader streamReader;
         try {
-            setInput(new InputStreamReader(src, charsetName));
+            streamReader = new InputStreamReader(src, charsetName);
         } catch (UnsupportedEncodingException e) {
             throw new IllegalArgumentException(e.getMessage());
         }
+        initialize(streamReader);
     }
 
     /**
@@ -220,7 +226,7 @@
         if (src == null) {
             throw new NullPointerException("src == null");
         }
-        setInput(src);
+        initialize(src);
     }
 
     /**
@@ -252,13 +258,14 @@
         if (charsetName == null) {
             throw new IllegalArgumentException("charsetName == null");
         }
-        setInput(Channels.newReader(src, charsetName));
+        initialize(Channels.newReader(src, charsetName));
     }
 
-    private void setInput(Readable input) {
+    private void initialize(Readable input) {
         this.input = input;
-        buffer.limit(0);
-        matcher = delimiter.matcher(buffer);
+        matcher = delimiter.matcher("");
+        matcher.useTransparentBounds(true);
+        matcher.useAnchoringBounds(false);
     }
 
     /**
@@ -535,7 +542,7 @@
         checkOpen();
         checkNotNull(pattern);
         matchSuccessful = false;
-        saveCurrentStatus();
+        prepareForScan();
         // if the next token exists, set the match region, otherwise return
         // false
         if (!setTokenRegion()) {
@@ -790,7 +797,7 @@
      * @throws IllegalStateException if this {@code Scanner} is closed.
      */
     public boolean hasNextLine() {
-        saveCurrentStatus();
+        prepareForScan();
         String result = findWithinHorizon(LINE_PATTERN, 0);
         recoverPreviousStatus();
         return result != null;
@@ -954,7 +961,7 @@
         checkOpen();
         checkNotNull(pattern);
         matchSuccessful = false;
-        saveCurrentStatus();
+        prepareForScan();
         if (!setTokenRegion()) {
             recoverPreviousStatus();
             // if setting match region fails
@@ -1204,7 +1211,7 @@
         Pattern floatPattern = getFloatPattern();
         String floatString = next(floatPattern);
         floatString = removeLocaleInfoFromFloat(floatString);
-        double doubleValue = 0;
+        double doubleValue;
         try {
             doubleValue = Double.parseDouble(floatString);
         } catch (NumberFormatException e) {
@@ -1248,7 +1255,7 @@
         Pattern floatPattern = getFloatPattern();
         String floatString = next(floatPattern);
         floatString = removeLocaleInfoFromFloat(floatString);
-        float floatValue = 0;
+        float floatValue;
         try {
             floatValue = Float.parseFloat(floatString);
         } catch (NumberFormatException e) {
@@ -1310,7 +1317,7 @@
         Pattern integerPattern = getIntegerPattern(radix);
         String intString = next(integerPattern);
         intString = removeLocaleInfo(intString, int.class);
-        int intValue = 0;
+        int intValue;
         try {
             intValue = Integer.parseInt(intString, radix);
         } catch (NumberFormatException e) {
@@ -1340,7 +1347,7 @@
         matcher.usePattern(LINE_PATTERN);
         matcher.region(findStartIndex, bufferLength);
 
-        String result = null;
+        String result;
         while (true) {
             if (matcher.find()) {
                 if (inputExhausted || matcher.end() != bufferLength
@@ -1422,7 +1429,7 @@
         Pattern integerPattern = getIntegerPattern(radix);
         String intString = next(integerPattern);
         intString = removeLocaleInfo(intString, int.class);
-        long longValue = 0;
+        long longValue;
         try {
             longValue = Long.parseLong(intString, radix);
         } catch (NumberFormatException e) {
@@ -1484,7 +1491,7 @@
         Pattern integerPattern = getIntegerPattern(radix);
         String intString = next(integerPattern);
         intString = removeLocaleInfo(intString, int.class);
-        short shortValue = 0;
+        short shortValue;
         try {
             shortValue = Short.parseShort(intString, radix);
         } catch (NumberFormatException e) {
@@ -1662,23 +1669,46 @@
     }
 
     /*
-     * Change the matcher's string after reading input
+     * Change the matcher's input after modifying the contents of the buffer.
+     * The current implementation of Matcher causes a copy of the buffer to be taken.
      */
     private void resetMatcher() {
-        if (matcher == null) {
-            matcher = delimiter.matcher(buffer);
-        } else {
-            matcher.reset(buffer);
-        }
-        matcher.useTransparentBounds(true);
-        matcher.useAnchoringBounds(false);
+        matcher.reset(buffer);
         matcher.region(findStartIndex, bufferLength);
     }
 
     /*
-     * Save the matcher's last find position
+     * Recover buffer space for characters that are already processed and save the matcher's state
+     * in case parsing fails. See recoverPrevousState. This method must be called before
+     * any buffer offsets are calculated.
      */
-    private void saveCurrentStatus() {
+    private void prepareForScan() {
+        // Compacting the buffer recovers space taken by already processed characters. This does not
+        // prevent the buffer growing in all situations but keeps the buffer small when delimiters
+        // exist regularly.
+        if (findStartIndex >= buffer.capacity() / 2) {
+            // When over half the buffer is filled with characters no longer being considered by the
+            // scanner we take the cost of compacting the buffer.
+
+            // Move all characters from [findStartIndex, findStartIndex + remaining()) to
+            // [0, remaining()).
+            int oldPosition = buffer.position();
+            buffer.position(findStartIndex);
+            buffer.compact();
+            buffer.position(oldPosition);
+
+            // Update Scanner state to reflect the new buffer state.
+            bufferLength -= findStartIndex;
+            findStartIndex = 0;
+            preStartIndex = -1;
+
+            // The matcher must also be informed that the buffer has changed because it operates on
+            // a String copy.
+            resetMatcher();
+        }
+
+        // Save the matcher's last find position so it can be returned to if the next token cannot
+        // be parsed.
         preStartIndex = findStartIndex;
     }
 
@@ -1822,7 +1852,7 @@
         boolean negative = removeLocaleSign(tokenBuilder);
         // Remove group separator
         String groupSeparator = String.valueOf(dfs.getGroupingSeparator());
-        int separatorIndex = -1;
+        int separatorIndex;
         while ((separatorIndex = tokenBuilder.indexOf(groupSeparator)) != -1) {
             tokenBuilder.delete(separatorIndex, separatorIndex + 1);
         }
@@ -1909,9 +1939,9 @@
      */
     private boolean setTokenRegion() {
         // The position where token begins
-        int tokenStartIndex = 0;
+        int tokenStartIndex;
         // The position where token ends
-        int tokenEndIndex = 0;
+        int tokenEndIndex;
         // Use delimiter pattern
         matcher.usePattern(delimiter);
         matcher.region(findStartIndex, bufferLength);
@@ -1945,8 +1975,7 @@
             if (matcher.find()) {
                 findComplete = true;
                 // If just delimiter remains
-                if (matcher.start() == findStartIndex
-                        && matcher.end() == bufferLength) {
+                if (matcher.start() == findStartIndex && matcher.end() == bufferLength) {
                     // If more input resource exists
                     if (!inputExhausted) {
                         readMore();
@@ -1964,7 +1993,7 @@
             }
         }
         tokenStartIndex = matcher.end();
-        findStartIndex = matcher.end();
+        findStartIndex = tokenStartIndex;
         return tokenStartIndex;
     }
 
@@ -1984,7 +2013,7 @@
             setSuccess = true;
         }
         // If the first delimiter of scanner is not at the find start position
-        if (-1 != findIndex && preStartIndex != matcher.start()) {
+        if (findIndex != -1 && preStartIndex != matcher.start()) {
             tokenStartIndex = preStartIndex;
             tokenEndIndex = matcher.start();
             findStartIndex = matcher.start();
@@ -1996,7 +2025,7 @@
     }
 
     private int findDelimiterAfter() {
-        int tokenEndIndex = 0;
+        int tokenEndIndex;
         boolean findComplete = false;
         while (!findComplete) {
             if (matcher.find()) {
@@ -2014,7 +2043,7 @@
             }
         }
         tokenEndIndex = matcher.start();
-        findStartIndex = matcher.start();
+        findStartIndex = tokenEndIndex;
         return tokenEndIndex;
     }
 
@@ -2032,7 +2061,7 @@
         }
 
         // Read input resource
-        int readCount = 0;
+        int readCount;
         try {
             buffer.limit(buffer.capacity());
             buffer.position(oldBufferLength);
diff --git a/luni/src/main/java/java/util/TimeZone.java b/luni/src/main/java/java/util/TimeZone.java
index e4c68c5..854a4a6 100644
--- a/luni/src/main/java/java/util/TimeZone.java
+++ b/luni/src/main/java/java/util/TimeZone.java
@@ -63,7 +63,7 @@
  *
  * @see Calendar
  * @see GregorianCalendar
- * @see SimpleDateFormat
+ * @see java.text.SimpleDateFormat
  */
 public abstract class TimeZone implements Serializable, Cloneable {
     private static final long serialVersionUID = 3581463369166924961L;
@@ -206,27 +206,48 @@
         // upgrade to icu4c 50 and rewrite the underlying native code. See also the
         // "element[j] != null" check in SimpleDateFormat.parseTimeZone, and the extra work in
         // DateFormatSymbols.getZoneStrings.
-
-        int offset = getRawOffset();
+        int offsetMillis = getRawOffset();
         if (daylightTime) {
-            offset += getDSTSavings();
+            offsetMillis += getDSTSavings();
         }
-        offset /= 60000;
+        return createGmtOffsetString(true /* includeGmt */, true /* includeMinuteSeparator */,
+                offsetMillis);
+    }
+
+    /**
+     * Returns a string representation of an offset from UTC.
+     *
+     * <p>The format is "[GMT](+|-)HH[:]MM". The output is not localized.
+     *
+     * @param includeGmt true to include "GMT", false to exclude
+     * @param includeMinuteSeparator true to include the separator between hours and minutes, false
+     *     to exclude.
+     * @param offsetMillis the offset from UTC
+     *
+     * @hide used internally by SimpleDateFormat
+     */
+    public static String createGmtOffsetString(boolean includeGmt,
+            boolean includeMinuteSeparator, int offsetMillis) {
+        int offsetMinutes = offsetMillis / 60000;
         char sign = '+';
-        if (offset < 0) {
+        if (offsetMinutes < 0) {
             sign = '-';
-            offset = -offset;
+            offsetMinutes = -offsetMinutes;
         }
         StringBuilder builder = new StringBuilder(9);
-        builder.append("GMT");
+        if (includeGmt) {
+            builder.append("GMT");
+        }
         builder.append(sign);
-        appendNumber(builder, 2, offset / 60);
-        builder.append(':');
-        appendNumber(builder, 2, offset % 60);
+        appendNumber(builder, 2, offsetMinutes / 60);
+        if (includeMinuteSeparator) {
+            builder.append(':');
+        }
+        appendNumber(builder, 2, offsetMinutes % 60);
         return builder.toString();
     }
 
-    private void appendNumber(StringBuilder builder, int count, int value) {
+    private static void appendNumber(StringBuilder builder, int count, int value) {
         String string = Integer.toString(value);
         for (int i = 0; i < count - string.length(); i++) {
             builder.append('0');
diff --git a/luni/src/main/java/libcore/util/EmptyArray.java b/luni/src/main/java/libcore/util/EmptyArray.java
index eb91589c..eac06f2 100644
--- a/luni/src/main/java/libcore/util/EmptyArray.java
+++ b/luni/src/main/java/libcore/util/EmptyArray.java
@@ -23,6 +23,7 @@
     public static final byte[] BYTE = new byte[0];
     public static final char[] CHAR = new char[0];
     public static final double[] DOUBLE = new double[0];
+    public static final float[] FLOAT = new float[0];
     public static final int[] INT = new int[0];
     public static final long[] LONG = new long[0];
 
diff --git a/luni/src/main/native/java_lang_Character.cpp b/luni/src/main/native/java_lang_Character.cpp
index 2d1fcfc..4022f4b 100644
--- a/luni/src/main/native/java_lang_Character.cpp
+++ b/luni/src/main/native/java_lang_Character.cpp
@@ -33,7 +33,7 @@
     return u_charType(codePoint);
 }
 
-static jbyte Character_getDirectionalityImpl(JNIEnv*, jclass, jint codePoint) {
+static jbyte Character_getIcuDirectionality(JNIEnv*, jclass, jint codePoint) {
     return u_charDirection(codePoint);
 }
 
@@ -166,7 +166,7 @@
 
 static JNINativeMethod gMethods[] = {
     NATIVE_METHOD(Character, digitImpl, "!(II)I"),
-    NATIVE_METHOD(Character, getDirectionalityImpl, "!(I)B"),
+    NATIVE_METHOD(Character, getIcuDirectionality, "!(I)B"),
     NATIVE_METHOD(Character, getNameImpl, "(I)Ljava/lang/String;"),
     NATIVE_METHOD(Character, getNumericValueImpl, "!(I)I"),
     NATIVE_METHOD(Character, getTypeImpl, "!(I)I"),
diff --git a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
index faf87f1..a7c9098 100644
--- a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
+++ b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
@@ -75,7 +75,6 @@
   const UDate now(Calendar::getNow());
 
   static const UnicodeString kUtc("UTC", 3, US_INV);
-  static const UnicodeString pacific_apia("Pacific/Apia", 12, US_INV);
 
   size_t id_count = env->GetArrayLength(result);
   for (size_t i = 0; i < id_count; ++i) {
@@ -104,11 +103,6 @@
       // every language).
       // TODO: check CLDR doesn't actually have this somewhere.
       long_std = short_std = long_dst = short_dst = kUtc;
-    } else if (zone_id.unicodeString() == pacific_apia) {
-      // icu4c 50 doesn't know Samoa has DST yet. http://b/7955614
-      if (long_dst.isBogus()) {
-        long_dst = "Samoa Daylight Time";
-      }
     }
 
     bool okay =
diff --git a/luni/src/test/java/libcore/java/lang/CharacterTest.java b/luni/src/test/java/libcore/java/lang/CharacterTest.java
index 94e3b96..8c6f06f 100644
--- a/luni/src/test/java/libcore/java/lang/CharacterTest.java
+++ b/luni/src/test/java/libcore/java/lang/CharacterTest.java
@@ -277,4 +277,16 @@
       }
     }
   }
+
+  // http://b/15492712
+  public void test_getDirectionality() throws Exception {
+    // We shouldn't throw an exception for any code point.
+    for (int c = '\u0000'; c <= Character.MAX_VALUE; ++c) {
+      Character.getDirectionality(c);
+    }
+    assertEquals(Character.DIRECTIONALITY_UNDEFINED, Character.getDirectionality(0x2066));
+    assertEquals(Character.DIRECTIONALITY_UNDEFINED, Character.getDirectionality(0x2067));
+    assertEquals(Character.DIRECTIONALITY_UNDEFINED, Character.getDirectionality(0x2068));
+    assertEquals(Character.DIRECTIONALITY_UNDEFINED, Character.getDirectionality(0x2069));
+  }
 }
diff --git a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
index 9ab6f70..057cd17 100644
--- a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
+++ b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
@@ -46,8 +46,8 @@
     }
 
     public void testSerialization() throws Exception {
-        // Set the default locale. The default locale determines what strings are used by the
-        // DateFormatSymbols after deserialization.
+        // Set the default locale. The default locale used to determine what strings were used by
+        // the DateFormatSymbols after deserialization. See http://b/16502916
         Locale.setDefault(Locale.US);
 
         // The Polish language needs stand-alone month and weekday names.
@@ -64,17 +64,16 @@
         DateFormatSymbols deserializedDfs = (DateFormatSymbols) in.readObject();
         assertEquals(-1, in.read());
 
-        // The two objects should claim to be equal, even though they aren't really.
+        // The two objects be equal.
         assertEquals(originalDfs, deserializedDfs);
 
         // The original differentiates between regular month names and stand-alone month names...
         assertEquals("stycznia", formatDate(pl, "MMMM", originalDfs));
         assertEquals("stycze\u0144", formatDate(pl, "LLLL", originalDfs));
 
-        // But the deserialized object is screwed because the RI's serialized form doesn't
-        // contain the locale or the necessary strings. Don't serialize DateFormatSymbols, folks!
+        // And so does the deserialized version.
         assertEquals("stycznia", formatDate(pl, "MMMM", deserializedDfs));
-        assertEquals("January", formatDate(pl, "LLLL", deserializedDfs));
+        assertEquals("stycze\u0144", formatDate(pl, "LLLL", deserializedDfs));
     }
 
     private String formatDate(Locale l, String fmt, DateFormatSymbols dfs) {