Merge "Adding a locale picker with two-step selection: language, then region."
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index fe41932..544444d 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -18,9 +18,11 @@
 
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.LineBreakBufferedWriter;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.io.Writer;
 import java.net.UnknownHostException;
 
 /**
@@ -126,7 +128,7 @@
      * @param tr An exception to log
      */
     public static int v(String tag, String msg, Throwable tr) {
-        return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
     }
 
     /**
@@ -147,7 +149,7 @@
      * @param tr An exception to log
      */
     public static int d(String tag, String msg, Throwable tr) {
-        return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
     }
 
     /**
@@ -168,7 +170,7 @@
      * @param tr An exception to log
      */
     public static int i(String tag, String msg, Throwable tr) {
-        return println_native(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
     }
 
     /**
@@ -189,7 +191,7 @@
      * @param tr An exception to log
      */
     public static int w(String tag, String msg, Throwable tr) {
-        return println_native(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
     }
 
     /**
@@ -219,7 +221,7 @@
      * @param tr An exception to log
      */
     public static int w(String tag, Throwable tr) {
-        return println_native(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
     }
 
     /**
@@ -240,7 +242,7 @@
      * @param tr An exception to log
      */
     public static int e(String tag, String msg, Throwable tr) {
-        return println_native(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
     }
 
     /**
@@ -292,8 +294,7 @@
         // Only mark this as ERROR, do not use ASSERT since that should be
         // reserved for cases where the system is guaranteed to abort.
         // The onTerribleFailure call does not always cause a crash.
-        int bytes = println_native(logId, ERROR, tag, msg + '\n'
-                + getStackTraceString(localStack ? what : tr));
+        int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
         sWtfHandler.onTerribleFailure(tag, what, system);
         return bytes;
     }
@@ -365,4 +366,107 @@
 
     /** @hide */ public static native int println_native(int bufID,
             int priority, String tag, String msg);
+
+    /**
+     * Return the maximum payload the log daemon accepts without truncation.
+     * @return LOGGER_ENTRY_MAX_PAYLOAD.
+     */
+    private static native int logger_entry_max_payload_native();
+
+    /**
+     * Helper function for long messages. Uses the LineBreakBufferedWriter to break
+     * up long messages and stacktraces along newlines, but tries to write in large
+     * chunks. This is to avoid truncation.
+     */
+    private static int printlns(int bufID, int priority, String tag, String msg,
+            Throwable tr) {
+        ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
+        // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
+        // and the length of the tag.
+        // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
+        //       is too expensive to compute that ahead of time.
+        int bufferSize = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD  // Base.
+                - 2                                                // Two terminators.
+                - (tag != null ? tag.length() : 0)                 // Tag length.
+                - 32;                                              // Some slack.
+        // At least assume you can print *some* characters (tag is not too large).
+        bufferSize = Math.max(bufferSize, 100);
+
+        LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
+
+        lbbw.println(msg);
+
+        if (tr != null) {
+            // This is to reduce the amount of log spew that apps do in the non-error
+            // condition of the network being unavailable.
+            Throwable t = tr;
+            while (t != null) {
+                if (t instanceof UnknownHostException) {
+                    break;
+                }
+                t = t.getCause();
+            }
+            if (t == null) {
+                tr.printStackTrace(lbbw);
+            }
+        }
+
+        lbbw.flush();
+
+        return logWriter.getWritten();
+    }
+
+    /**
+     * NoPreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
+     * a JNI call during logging.
+     */
+    static class NoPreloadHolder {
+        public final static int LOGGER_ENTRY_MAX_PAYLOAD =
+                logger_entry_max_payload_native();
+    }
+
+    /**
+     * Helper class to write to the logcat. Different from LogWriter, this writes
+     * the whole given buffer and does not break along newlines.
+     */
+    private static class ImmediateLogWriter extends Writer {
+
+        private int bufID;
+        private int priority;
+        private String tag;
+
+        private int written = 0;
+
+        /**
+         * Create a writer that immediately writes to the log, using the given
+         * parameters.
+         */
+        public ImmediateLogWriter(int bufID, int priority, String tag) {
+            this.bufID = bufID;
+            this.priority = priority;
+            this.tag = tag;
+        }
+
+        public int getWritten() {
+            return written;
+        }
+
+        @Override
+        public void write(char[] cbuf, int off, int len) {
+            // Note: using String here has a bit of overhead as a Java object is created,
+            //       but using the char[] directly is not easier, as it needs to be translated
+            //       to a C char[] for logging.
+            written += println_native(bufID, priority, tag, new String(cbuf, off, len));
+        }
+
+        @Override
+        public void flush() {
+            // Ignored.
+        }
+
+        @Override
+        public void close() {
+            // Ignored.
+        }
+    }
 }
diff --git a/core/java/com/android/internal/util/LineBreakBufferedWriter.java b/core/java/com/android/internal/util/LineBreakBufferedWriter.java
new file mode 100644
index 0000000..f831e7a
--- /dev/null
+++ b/core/java/com/android/internal/util/LineBreakBufferedWriter.java
@@ -0,0 +1,293 @@
+/*
+ * 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 com.android.internal.util;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.Arrays;
+
+/**
+ * A writer that breaks up its output into chunks before writing to its out writer,
+ * and which is linebreak aware, i.e., chunks will created along line breaks, if
+ * possible.
+ *
+ * Note: this class is not thread-safe.
+ */
+public class LineBreakBufferedWriter extends PrintWriter {
+
+    /**
+     * A buffer to collect data until the buffer size is reached.
+     *
+     * Note: we manage a char[] ourselves to avoid an allocation when printing to the
+     *       out writer. Otherwise a StringBuilder would have been simpler to use.
+     */
+    private char[] buffer;
+
+    /**
+     * The index of the first free element in the buffer.
+     */
+    private int bufferIndex;
+
+    /**
+     * The chunk size (=maximum buffer size) to use for this writer.
+     */
+    private final int bufferSize;
+
+
+    /**
+     * Index of the last newline character discovered in the buffer. The writer will try
+     * to split there.
+     */
+    private int lastNewline = -1;
+
+    /**
+     * The line separator for println().
+     */
+    private final String lineSeparator;
+
+    /**
+     * Create a new linebreak-aware buffered writer with the given output and buffer
+     * size. The initial capacity will be a default value.
+     * @param out The writer to write to.
+     * @param bufferSize The maximum buffer size.
+     */
+    public LineBreakBufferedWriter(Writer out, int bufferSize) {
+        this(out, bufferSize, 16);  // 16 is the default size of a StringBuilder buffer.
+    }
+
+    /**
+     * Create a new linebreak-aware buffered writer with the given output, buffer
+     * size and initial capacity.
+     * @param out The writer to write to.
+     * @param bufferSize The maximum buffer size.
+     * @param initialCapacity The initial capacity of the internal buffer.
+     */
+    public LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity) {
+        super(out);
+        this.buffer = new char[Math.min(initialCapacity, bufferSize)];
+        this.bufferIndex = 0;
+        this.bufferSize = bufferSize;
+        this.lineSeparator = System.getProperty("line.separator");
+    }
+
+    /**
+     * Flush the current buffer. This will ignore line breaks.
+     */
+    @Override
+    public void flush() {
+        writeBuffer(bufferIndex);
+        bufferIndex = 0;
+        super.flush();
+    }
+
+    @Override
+    public void write(int c) {
+        if (bufferIndex < bufferSize) {
+            buffer[bufferIndex] = (char)c;
+            bufferIndex++;
+            if ((char)c == '\n') {
+                lastNewline = bufferIndex;
+            }
+        } else {
+            // This should be an uncommon case, we mostly expect char[] and String. So
+            // let the chunking be handled by the char[] case.
+            write(new char[] { (char)c }, 0 ,1);
+        }
+    }
+
+    @Override
+    public void println() {
+        write(lineSeparator);
+    }
+
+    @Override
+    public void write(char[] buf, int off, int len) {
+        while (bufferIndex + len > bufferSize) {
+            // Find the next newline in the buffer, see if that's below the limit.
+            // Repeat.
+            int nextNewLine = -1;
+            int maxLength = bufferSize - bufferIndex;
+            for (int i = 0; i < maxLength; i++) {
+                if (buf[off + i] == '\n') {
+                    if (bufferIndex + i < bufferSize) {
+                        nextNewLine = i;
+                    } else {
+                        break;
+                    }
+                }
+            }
+
+            if (nextNewLine != -1) {
+                // We can add some more data.
+                appendToBuffer(buf, off, nextNewLine);
+                writeBuffer(bufferIndex);
+                bufferIndex = 0;
+                lastNewline = -1;
+                off += nextNewLine + 1;
+                len -= nextNewLine + 1;
+            } else if (lastNewline != -1) {
+                // Use the last newline.
+                writeBuffer(lastNewline);
+                removeFromBuffer(lastNewline + 1);
+                lastNewline = -1;
+            } else {
+                // OK, there was no newline, break at a full buffer.
+                int rest = bufferSize - bufferIndex;
+                appendToBuffer(buf, off, rest);
+                writeBuffer(bufferIndex);
+                bufferIndex = 0;
+                off += rest;
+                len -= rest;
+            }
+        }
+
+        // Add to the buffer, this will fit.
+        if (len > 0) {
+            // Add the chars, find the last newline.
+            appendToBuffer(buf, off, len);
+            for (int i = len - 1; i >= 0; i--) {
+                if (buf[off + i] == '\n') {
+                    lastNewline = bufferIndex - len + i;
+                    break;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void write(String s, int off, int len) {
+        while (bufferIndex + len > bufferSize) {
+            // Find the next newline in the buffer, see if that's below the limit.
+            // Repeat.
+            int nextNewLine = -1;
+            int maxLength = bufferSize - bufferIndex;
+            for (int i = 0; i < maxLength; i++) {
+                if (s.charAt(off + i) == '\n') {
+                    if (bufferIndex + i < bufferSize) {
+                        nextNewLine = i;
+                    } else {
+                        break;
+                    }
+                }
+            }
+
+            if (nextNewLine != -1) {
+                // We can add some more data.
+                appendToBuffer(s, off, nextNewLine);
+                writeBuffer(bufferIndex);
+                bufferIndex = 0;
+                lastNewline = -1;
+                off += nextNewLine + 1;
+                len -= nextNewLine + 1;
+            } else if (lastNewline != -1) {
+                // Use the last newline.
+                writeBuffer(lastNewline);
+                removeFromBuffer(lastNewline + 1);
+                lastNewline = -1;
+            } else {
+                // OK, there was no newline, break at a full buffer.
+                int rest = bufferSize - bufferIndex;
+                appendToBuffer(s, off, rest);
+                writeBuffer(bufferIndex);
+                bufferIndex = 0;
+                off += rest;
+                len -= rest;
+            }
+        }
+
+        // Add to the buffer, this will fit.
+        if (len > 0) {
+            // Add the chars, find the last newline.
+            appendToBuffer(s, off, len);
+            for (int i = len - 1; i >= 0; i--) {
+                if (s.charAt(off + i) == '\n') {
+                    lastNewline = bufferIndex - len + i;
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Append the characters to the buffer. This will potentially resize the buffer,
+     * and move the index along.
+     * @param buf The char[] containing the data.
+     * @param off The start index to copy from.
+     * @param len The number of characters to copy.
+     */
+    private void appendToBuffer(char[] buf, int off, int len) {
+        if (bufferIndex + len > buffer.length) {
+            ensureCapacity(bufferIndex + len);
+        }
+        System.arraycopy(buf, off, buffer, bufferIndex, len);
+        bufferIndex += len;
+    }
+
+    /**
+     * Append the characters from the given string to the buffer. This will potentially
+     * resize the buffer, and move the index along.
+     * @param s The string supplying the characters.
+     * @param off The start index to copy from.
+     * @param len The number of characters to copy.
+     */
+    private void appendToBuffer(String s, int off, int len) {
+        if (bufferIndex + len > buffer.length) {
+            ensureCapacity(bufferIndex + len);
+        }
+        s.getChars(off, off + len, buffer, bufferIndex);
+        bufferIndex += len;
+    }
+
+    /**
+     * Resize the buffer. We use the usual double-the-size plus constant scheme for
+     * amortized O(1) insert. Note: we expect small buffers, so this won't check for
+     * overflow.
+     * @param capacity The size to be ensured.
+     */
+    private void ensureCapacity(int capacity) {
+        int newSize = buffer.length * 2 + 2;
+        if (newSize < capacity) {
+            newSize = capacity;
+        }
+        buffer = Arrays.copyOf(buffer, newSize);
+    }
+
+    /**
+     * Remove the characters up to (and excluding) index i from the buffer. This will
+     * not resize the buffer, but will update bufferIndex.
+     * @param i The number of characters to remove from the front.
+     */
+    private void removeFromBuffer(int i) {
+        int rest = bufferIndex - i;
+        if (rest > 0) {
+            System.arraycopy(buffer, bufferIndex - rest, buffer, 0, rest);
+            bufferIndex = rest;
+        } else {
+            bufferIndex = 0;
+        }
+    }
+
+    /**
+     * Helper method, write the given part of the buffer, [start,length), to the output.
+     * @param length The number of characters to flush.
+     */
+    private void writeBuffer(int length) {
+        if (length > 0) {
+            super.write(buffer, 0, length);
+        }
+    }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index a9a198b..3bde6b3 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -184,6 +184,8 @@
     $(call include-path-for, libhardware_legacy)/hardware_legacy \
     $(TOP)/frameworks/av/include \
     $(TOP)/frameworks/base/media/jni \
+    $(TOP)/system/core/base/include \
+    $(TOP)/system/core/include \
     $(TOP)/system/media/camera/include \
     $(TOP)/system/netd/include \
     external/pdfium/core/include/fpdfapi \
diff --git a/core/jni/android_util_Log.cpp b/core/jni/android_util_Log.cpp
index 2d23cda..7719e31 100644
--- a/core/jni/android_util_Log.cpp
+++ b/core/jni/android_util_Log.cpp
@@ -18,8 +18,10 @@
 #define LOG_NAMESPACE "log.tag."
 #define LOG_TAG "Log_println"
 
+#include <android-base/macros.h>
 #include <assert.h>
 #include <cutils/properties.h>
+#include <log/logger.h>               // For LOGGER_ENTRY_MAX_PAYLOAD.
 #include <utils/Log.h>
 #include <utils/String8.h>
 
@@ -109,12 +111,23 @@
 }
 
 /*
+ * In class android.util.Log:
+ *  private static native int logger_entry_max_payload_native()
+ */
+static jint android_util_Log_logger_entry_max_payload_native(JNIEnv* env ATTRIBUTE_UNUSED,
+                                                             jobject clazz ATTRIBUTE_UNUSED)
+{
+    return static_cast<jint>(LOGGER_ENTRY_MAX_PAYLOAD);
+}
+
+/*
  * JNI registration.
  */
 static const JNINativeMethod gMethods[] = {
     /* name, signature, funcPtr */
     { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
     { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
+    { "logger_entry_max_payload_native",  "()I", (void*) android_util_Log_logger_entry_max_payload_native },
 };
 
 int register_android_util_Log(JNIEnv* env)
diff --git a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
new file mode 100644
index 0000000..49ae104
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2013 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 com.android.internal.util;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Tests for {@link IndentingPrintWriter}.
+ */
+public class LineBreakBufferedWriterTest extends TestCase {
+
+    private ByteArrayOutputStream mStream;
+    private RecordingWriter mWriter;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mWriter = new RecordingWriter();
+    }
+
+    public void testLessThanBufferSize() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 1000);
+
+        lw.println("Hello");
+        lw.println("World");
+        lw.println("Test");
+        lw.flush();
+
+        assertOutput("Hello\nWorld\nTest\n");
+    }
+
+    public void testMoreThanBufferSizeNoLineBreaks() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);
+
+        String literal = "aaaaaaaaaaaaaaa";
+        lw.print(literal);
+        lw.print(literal);
+        lw.flush();
+
+        // Have to manually inspect output.
+        List<String> result = mWriter.getStrings();
+        // Expect two strings.
+        assertEquals(2, result.size());
+        // Expect the strings to sum up to the original input.
+        assertEquals(2 * literal.length(), result.get(0).length() + result.get(1).length());
+        // Strings should only be a.
+        for (String s : result) {
+            for (int i = 0; i < s.length(); i++) {
+                assertEquals('a', s.charAt(i));
+            }
+        }
+    }
+
+    public void testMoreThanBufferSizeNoLineBreaksSingleString() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);
+
+        String literal = "aaaaaaaaaaaaaaa";
+        lw.print(literal + literal);
+        lw.flush();
+
+        // Have to manually inspect output.
+        List<String> result = mWriter.getStrings();
+        // Expect two strings.
+        assertEquals(2, result.size());
+        // Expect the strings to sum up to the original input.
+        assertEquals(2 * literal.length(), result.get(0).length() + result.get(1).length());
+        // Strings should only be a.
+        for (String s : result) {
+            for (int i = 0; i < s.length(); i++) {
+                assertEquals('a', s.charAt(i));
+            }
+        }
+    }
+
+    public void testMoreThanBufferSizeLineBreakBefore() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);
+
+        String literal1 = "aaaaaaaaaa\nbbbb";
+        String literal2 = "cccccccccc";
+        lw.print(literal1);
+        lw.print(literal2);
+        lw.flush();
+
+        assertOutput("aaaaaaaaaa", "bbbbcccccccccc");
+    }
+
+    public void testMoreThanBufferSizeLineBreakBeforeSingleString() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);
+
+        String literal1 = "aaaaaaaaaa\nbbbb";
+        String literal2 = "cccccccccc";
+        lw.print(literal1 + literal2);
+        lw.flush();
+
+        assertOutput("aaaaaaaaaa", "bbbbcccccccccc");
+    }
+
+    public void testMoreThanBufferSizeLineBreakNew() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);
+
+        String literal1 = "aaaaaaaaaabbbbb";
+        String literal2 = "c\nd\nddddddddd";
+        lw.print(literal1);
+        lw.print(literal2);
+        lw.flush();
+
+        assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd");
+    }
+
+    public void testMoreThanBufferSizeLineBreakBeforeAndNew() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);
+
+        String literal1 = "aaaaaaaaaa\nbbbbb";
+        String literal2 = "c\nd\nddddddddd";
+        lw.print(literal1);
+        lw.print(literal2);
+        lw.flush();
+
+        assertOutput("aaaaaaaaaa\nbbbbbc\nd", "ddddddddd");
+    }
+
+    public void testMoreThanBufferSizeInt() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 15);
+
+        int literal1 = 1234567890;
+        int literal2 = 987654321;
+        lw.print(literal1);
+        lw.print(literal2);
+        lw.flush();
+
+        assertOutput("123456789098765", "4321");
+    }
+
+    public void testMoreThanBufferSizeChar() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 15);
+
+        for(int i = 0; i < 10; i++) {
+            lw.print('$');
+        }
+        for(int i = 0; i < 10; i++) {
+            lw.print('%');
+        }
+        lw.flush();
+
+        assertOutput("$$$$$$$$$$%%%%%", "%%%%%");
+    }
+
+    public void testMoreThanBufferSizeLineBreakNewChars() {
+        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);
+
+        String literal1 = "aaaaaaaaaabbbbb";
+        String literal2 = "c\nd\nddddddddd";
+        lw.print(literal1.toCharArray());
+        lw.print(literal2.toCharArray());
+        lw.flush();
+
+        assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd");
+    }
+
+    private void assertOutput(String... golden) {
+        List<String> goldList = createTestGolden(golden);
+        assertEquals(goldList, mWriter.getStrings());
+    }
+
+    private static List<String> createTestGolden(String... args) {
+        List<String> ret = new ArrayList<String>();
+        for (String s : args) {
+            ret.add(s);
+        }
+        return ret;
+    }
+
+    // A writer recording calls to write.
+    private final static class RecordingWriter extends Writer {
+
+        private List<String> strings = new ArrayList<String>();
+
+        public RecordingWriter() {
+        }
+
+        public List<String> getStrings() {
+            return strings;
+        }
+
+        @Override
+        public void write(char[] cbuf, int off, int len) {
+            strings.add(new String(cbuf, off, len));
+        }
+
+        @Override
+        public void flush() {
+            // Ignore.
+        }
+
+        @Override
+        public void close() {
+            // Ignore.
+        }
+    }
+}