OSDN Git Service

Merge "Sort the result of SpannableStringBuilder.getSpans"
authorSiyamed Sinir <siyamed@google.com>
Thu, 21 Jan 2016 19:12:28 +0000 (19:12 +0000)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Thu, 21 Jan 2016 19:12:28 +0000 (19:12 +0000)
core/java/android/text/Layout.java
core/java/android/text/SpannableStringBuilder.java
core/java/android/text/SpannableStringInternal.java
core/tests/benchmarks/src/android/text/SpannableStringBuilderBenchmark.java [new file with mode: 0644]
core/tests/benchmarks/src/android/text/SpannableStringInternalCopyBenchmark.java [new file with mode: 0644]

index 2c4241b..692d848 100644 (file)
@@ -1826,7 +1826,11 @@ public abstract class Layout {
             return ArrayUtils.emptyArray(type);
         }
 
-        return text.getSpans(start, end, type);
+        if(text instanceof SpannableStringBuilder) {
+            return ((SpannableStringBuilder) text).getSpans(start, end, type, false);
+        } else {
+            return text.getSpans(start, end, type);
+        }
     }
 
     private char getEllipsisChar(TextUtils.TruncateAt method) {
index 4267238..e34560b 100644 (file)
@@ -19,6 +19,7 @@ package android.text;
 import android.annotation.Nullable;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.text.style.ParagraphStyle;
 import android.util.Log;
 
 import com.android.internal.util.ArrayUtils;
@@ -66,11 +67,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
         TextUtils.getChars(text, start, end, mText, 0);
 
         mSpanCount = 0;
+        mSpanInsertCount = 0;
         mSpans = EmptyArray.OBJECT;
         mSpanStarts = EmptyArray.INT;
         mSpanEnds = EmptyArray.INT;
         mSpanFlags = EmptyArray.INT;
         mSpanMax = EmptyArray.INT;
+        mSpanOrder = EmptyArray.INT;
+        mPrioSortBuffer = EmptyArray.INT;
+        mOrderSortBuffer = EmptyArray.INT;
 
         if (text instanceof Spanned) {
             Spanned sp = (Spanned) text;
@@ -234,6 +239,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
     // Documentation from interface
     public void clear() {
         replace(0, length(), "", 0, 0);
+        mSpanInsertCount = 0;
     }
 
     // Documentation from interface
@@ -256,6 +262,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
         if (mIndexOfSpan != null) {
             mIndexOfSpan.clear();
         }
+        mSpanInsertCount = 0;
     }
 
     // Documentation from interface
@@ -485,6 +492,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
         System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
         System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
         System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
+        System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count);
 
         mSpanCount--;
 
@@ -712,9 +720,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
                 end += mGapLength;
         }
 
-        int count = mSpanCount;
-        Object[] spans = mSpans;
-
         if (mIndexOfSpan != null) {
             Integer index = mIndexOfSpan.get(what);
             if (index != null) {
@@ -744,8 +749,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
         mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
         mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
         mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
+        mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount);
         invalidateIndex(mSpanCount);
         mSpanCount++;
+        mSpanInsertCount++;
         // Make sure there is enough room for empty interior nodes.
         // This magic formula computes the size of the smallest perfect binary
         // tree no smaller than mSpanCount.
@@ -837,6 +844,25 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
      */
     @SuppressWarnings("unchecked")
     public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) {
+        return getSpans(queryStart, queryEnd, kind, true);
+    }
+
+    /**
+     * Return an array of the spans of the specified type that overlap
+     * the specified range of the buffer.  The kind may be Object.class to get
+     * a list of all the spans regardless of type.
+     *
+     * @param queryStart Start index.
+     * @param queryEnd End index.
+     * @param kind Class type to search for.
+     * @param sort If true the results are sorted by the insertion order.
+     * @param <T>
+     * @return Array of the spans. Empty array if no results are found.
+     *
+     * @hide
+     */
+    public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind,
+                                 boolean sort) {
         if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
         if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
         int count = countSpans(queryStart, queryEnd, kind, treeRoot());
@@ -846,7 +872,13 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
 
         // Safe conversion, but requires a suppressWarning
         T[] ret = (T[]) Array.newInstance(kind, count);
-        getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, 0);
+        if (sort) {
+            mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count);
+            mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count);
+        }
+        getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer,
+                mOrderSortBuffer, 0, sort);
+        if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer);
         return ret;
     }
 
@@ -876,7 +908,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
                 if (spanEnd >= queryStart &&
                     (spanStart == spanEnd || queryStart == queryEnd ||
                         (spanStart != queryEnd && spanEnd != queryStart)) &&
-                        kind.isInstance(mSpans[i])) {
+                        (Object.class == kind || kind.isInstance(mSpans[i]))) {
                     count++;
                 }
                 if ((i & 1) != 0) {
@@ -887,9 +919,25 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
         return count;
     }
 
+    /**
+     * Fills the result array with the spans found under the current interval tree node.
+     *
+     * @param queryStart Start index for the interval query.
+     * @param queryEnd End index for the interval query.
+     * @param kind Class type to search for.
+     * @param i Index of the current tree node.
+     * @param ret Array to be filled with results.
+     * @param priority Buffer to keep record of the priorities of spans found.
+     * @param insertionOrder Buffer to keep record of the insertion orders of spans found.
+     * @param count The number of found spans.
+     * @param sort Flag to fill the priority and insertion order buffers. If false then
+     *             the spans with priority flag will be sorted in the result array.
+     * @param <T>
+     * @return The total number of spans found.
+     */
     @SuppressWarnings("unchecked")
     private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind,
-            int i, T[] ret, int count) {
+            int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort) {
         if ((i & 1) != 0) {
             // internal tree node
             int left = leftChild(i);
@@ -898,7 +946,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
                 spanMax -= mGapLength;
             }
             if (spanMax >= queryStart) {
-                count = getSpansRec(queryStart, queryEnd, kind, left, ret, count);
+                count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority,
+                        insertionOrder, count, sort);
             }
         }
         if (i >= mSpanCount) return count;
@@ -914,36 +963,137 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
             if (spanEnd >= queryStart &&
                     (spanStart == spanEnd || queryStart == queryEnd ||
                         (spanStart != queryEnd && spanEnd != queryStart)) &&
-                        kind.isInstance(mSpans[i])) {
-                int prio = mSpanFlags[i] & SPAN_PRIORITY;
-                if (prio != 0) {
-                    int j;
-
-                    for (j = 0; j < count; j++) {
+                        (Object.class == kind || kind.isInstance(mSpans[i]))) {
+                int spanPriority = mSpanFlags[i] & SPAN_PRIORITY;
+                if(sort) {
+                    ret[count] = (T) mSpans[i];
+                    priority[count] = spanPriority;
+                    insertionOrder[count] = mSpanOrder[i];
+                } else if (spanPriority != 0) {
+                    //insertion sort for elements with priority
+                    int j = 0;
+                    for (; j < count; j++) {
                         int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
-
-                        if (prio > p) {
-                            break;
-                        }
+                        if (spanPriority > p) break;
                     }
-
                     System.arraycopy(ret, j, ret, j + 1, count - j);
-                    // Safe conversion thanks to the isInstance test above
                     ret[j] = (T) mSpans[i];
-                } else {
-                    // Safe conversion thanks to the isInstance test above
-                    ret[count] = (T) mSpans[i];
                 }
                 count++;
             }
             if (count < ret.length && (i & 1) != 0) {
-                count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, count);
+                count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority,
+                        insertionOrder, count, sort);
             }
         }
         return count;
     }
 
     /**
+     * Check the size of the buffer and grow if required.
+     *
+     * @param buffer Buffer to be checked.
+     * @param size Required size.
+     * @return Same buffer instance if the current size is greater than required size. Otherwise a
+     * new instance is created and returned.
+     */
+    private final int[] checkSortBuffer(int[] buffer, int size) {
+        if(size > buffer.length) {
+            return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size));
+        }
+        return buffer;
+    }
+
+    /**
+     * An iterative heap sort implementation. It will sort the spans using first their priority
+     * then insertion order. A span with higher priority will be before a span with lower
+     * priority. If priorities are the same, the spans will be sorted with insertion order. A
+     * span with a lower insertion order will be before a span with a higher insertion order.
+     *
+     * @param array Span array to be sorted.
+     * @param priority Priorities of the spans
+     * @param insertionOrder Insertion orders of the spans
+     * @param <T> Span object type.
+     * @param <T>
+     */
+    private final <T> void sort(T[] array, int[] priority, int[] insertionOrder) {
+        int size = array.length;
+        for (int i = size / 2 - 1; i >= 0; i--) {
+            siftDown(i, array, size, priority, insertionOrder);
+        }
+
+        for (int i = size - 1; i > 0; i--) {
+            T v = array[0];
+            int prio = priority[0];
+            int insertOrder = insertionOrder[0];
+            array[0] = array[i];
+            priority[0] = priority[i];
+            insertionOrder[0] = insertionOrder[i];
+            siftDown(0, array, i, priority, insertionOrder);
+            array[i] = v;
+            priority[i] = prio;
+            insertionOrder[i] = insertOrder;
+        }
+    }
+
+    /**
+     * Helper function for heap sort.
+     *
+     * @param index Index of the element to sift down.
+     * @param array Span array to be sorted.
+     * @param size Current heap size.
+     * @param priority Priorities of the spans
+     * @param insertionOrder Insertion orders of the spans
+     * @param <T> Span object type.
+     */
+    private final <T> void siftDown(int index, T[] array, int size, int[] priority,
+                                    int[] insertionOrder) {
+        T v = array[index];
+        int prio = priority[index];
+        int insertOrder = insertionOrder[index];
+
+        int left = 2 * index + 1;
+        while (left < size) {
+            if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) {
+                left++;
+            }
+            if (compareSpans(index, left, priority, insertionOrder) >= 0) {
+                break;
+            }
+            array[index] = array[left];
+            priority[index] = priority[left];
+            insertionOrder[index] = insertionOrder[left];
+            index = left;
+            left = 2 * index + 1;
+        }
+        array[index] = v;
+        priority[index] = prio;
+        insertionOrder[index] = insertOrder;
+    }
+
+    /**
+     * Compare two span elements in an array. Comparison is based first on the priority flag of
+     * the span, and then the insertion order of the span.
+     *
+     * @param left Index of the element to compare.
+     * @param right Index of the other element to compare.
+     * @param priority Priorities of the spans
+     * @param insertionOrder Insertion orders of the spans
+     * @return
+     */
+    private final int compareSpans(int left, int right, int[] priority,
+                                       int[] insertionOrder) {
+        int priority1 = priority[left];
+        int priority2 = priority[right];
+        if (priority1 == priority2) {
+            return Integer.compare(insertionOrder[left], insertionOrder[right]);
+        }
+        // since high priority has to be before a lower priority, the arguments to compare are
+        // opposite of the insertion order check.
+        return Integer.compare(priority2, priority1);
+    }
+
+    /**
      * Return the next offset after <code>start</code> but less than or
      * equal to <code>limit</code> where a span of the specified type
      * begins or ends.
@@ -1509,18 +1659,21 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
                 int start = mSpanStarts[i];
                 int end = mSpanEnds[i];
                 int flags = mSpanFlags[i];
+                int insertionOrder = mSpanOrder[i];
                 int j = i;
                 do {
                     mSpans[j] = mSpans[j - 1];
                     mSpanStarts[j] = mSpanStarts[j - 1];
                     mSpanEnds[j] = mSpanEnds[j - 1];
                     mSpanFlags[j] = mSpanFlags[j - 1];
+                    mSpanOrder[j] = mSpanOrder[j - 1];
                     j--;
                 } while (j > 0 && start < mSpanStarts[j - 1]);
                 mSpans[j] = span;
                 mSpanStarts[j] = start;
                 mSpanEnds[j] = end;
                 mSpanFlags[j] = flags;
+                mSpanOrder[j] = insertionOrder;
                 invalidateIndex(j);
             }
         }
@@ -1558,6 +1711,11 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
     private int[] mSpanEnds;
     private int[] mSpanMax;  // see calcMax() for an explanation of what this array stores
     private int[] mSpanFlags;
+    private int[] mSpanOrder;  // store the order of span insertion
+    private int mSpanInsertCount;  // counter for the span insertion
+    private int[] mPrioSortBuffer;  // buffer used to sort getSpans result
+    private int[] mOrderSortBuffer;  // buffer used to sort getSpans result
+
     private int mSpanCount;
     private IdentityHashMap<Object, Integer> mIndexOfSpan;
     private int mLowWaterMark;  // indices below this have not been touched
index 5c5deb4..47e71be 100644 (file)
@@ -36,24 +36,99 @@ import java.lang.reflect.Array;
         mSpanData = EmptyArray.INT;
 
         if (source instanceof Spanned) {
-            Spanned sp = (Spanned) source;
-            Object[] spans = sp.getSpans(start, end, Object.class);
+            if (source instanceof SpannableStringInternal) {
+                copySpans((SpannableStringInternal) source, start, end);
+            } else {
+                copySpans((Spanned) source, start, end);
+            }
+        }
+    }
 
-            for (int i = 0; i < spans.length; i++) {
-                int st = sp.getSpanStart(spans[i]);
-                int en = sp.getSpanEnd(spans[i]);
-                int fl = sp.getSpanFlags(spans[i]);
+    /**
+     * Copies another {@link Spanned} object's spans between [start, end] into this object.
+     *
+     * @param src Source object to copy from.
+     * @param start Start index in the source object.
+     * @param end End index in the source object.
+     */
+    private final void copySpans(Spanned src, int start, int end) {
+        Object[] spans = src.getSpans(start, end, Object.class);
+
+        for (int i = 0; i < spans.length; i++) {
+            int st = src.getSpanStart(spans[i]);
+            int en = src.getSpanEnd(spans[i]);
+            int fl = src.getSpanFlags(spans[i]);
+
+            if (st < start)
+                st = start;
+            if (en > end)
+                en = end;
+
+            setSpan(spans[i], st - start, en - start, fl);
+        }
+    }
 
-                if (st < start)
-                    st = start;
-                if (en > end)
-                    en = end;
+    /**
+     * Copies a {@link SpannableStringInternal} object's spans between [start, end] into this
+     * object.
+     *
+     * @param src Source object to copy from.
+     * @param start Start index in the source object.
+     * @param end End index in the source object.
+     */
+    private final void copySpans(SpannableStringInternal src, int start, int end) {
+        if (start == 0 && end == src.length()) {
+            mSpans = ArrayUtils.newUnpaddedObjectArray(src.mSpans.length);
+            mSpanData = new int[src.mSpanData.length];
+            mSpanCount = src.mSpanCount;
+            System.arraycopy(src.mSpans, 0, mSpans, 0, src.mSpans.length);
+            System.arraycopy(src.mSpanData, 0, mSpanData, 0, mSpanData.length);
+        } else {
+            int count = 0;
+            int[] srcData = src.mSpanData;
+            int limit = src.mSpanCount;
+            for (int i = 0; i < limit; i++) {
+                int spanStart = srcData[i * COLUMNS + START];
+                int spanEnd = srcData[i * COLUMNS + END];
+                if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
+                count++;
+            }
 
-                setSpan(spans[i], st - start, en - start, fl);
+            if (count == 0) return;
+
+            Object[] srcSpans = src.mSpans;
+            mSpanCount = count;
+            mSpans = ArrayUtils.newUnpaddedObjectArray(mSpanCount);
+            mSpanData = new int[mSpanCount * COLUMNS];
+            for (int i = 0, j = 0; i < limit; i++) {
+                int spanStart = srcData[i * COLUMNS + START];
+                int spanEnd = srcData[i * COLUMNS + END];
+                if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
+                if (spanStart < start) spanStart = start;
+                if (spanEnd > end) spanEnd = end;
+
+                mSpans[j] = srcSpans[i];
+                mSpanData[j * COLUMNS + START] = spanStart - start;
+                mSpanData[j * COLUMNS + END] = spanEnd - start;
+                mSpanData[j * COLUMNS + FLAGS] = srcData[i * COLUMNS + FLAGS];
+                j++;
             }
         }
     }
 
+    /**
+     * Checks if [spanStart, spanEnd] interval is excluded from [start, end].
+     *
+     * @return True if excluded, false if included.
+     */
+    private final boolean isOutOfCopyRange(int start, int end, int spanStart, int spanEnd) {
+        if (spanStart > end || spanEnd < start) return true;
+        if (spanStart != spanEnd && start != end) {
+            if (spanStart == end || spanEnd == start) return true;
+        }
+        return false;
+    }
+
     public final int length() {
         return mText.length();
     }
@@ -234,7 +309,7 @@ import java.lang.reflect.Array;
             }
 
             // verify span class as late as possible, since it is expensive
-            if (kind != null && !kind.isInstance(spans[i])) {
+            if (kind != null && kind != Object.class && !kind.isInstance(spans[i])) {
                 continue;
             }
 
diff --git a/core/tests/benchmarks/src/android/text/SpannableStringBuilderBenchmark.java b/core/tests/benchmarks/src/android/text/SpannableStringBuilderBenchmark.java
new file mode 100644 (file)
index 0000000..23f8810
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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 android.text;
+
+import com.google.caliper.AfterExperiment;
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
+import com.google.caliper.Param;
+
+public class SpannableStringBuilderBenchmark {
+
+    @Param({"android.text.style.ImageSpan",
+            "android.text.style.ParagraphStyle",
+            "android.text.style.CharacterStyle",
+            "java.lang.Object"})
+    private String paramType;
+
+    @Param({"1", "4", "16"})
+    private String paramStringMult;
+
+    private Class clazz;
+    private SpannableStringBuilder builder;
+
+    @BeforeExperiment
+    protected void setUp() throws Exception {
+        clazz = Class.forName(paramType);
+        int strSize = Integer.valueOf(paramStringMult);
+        StringBuilder strBuilder = new StringBuilder();
+        for (int i = 0; i < strSize; i++) {
+            strBuilder.append(TEST_STRING);
+        }
+        builder = new SpannableStringBuilder(Html.fromHtml(strBuilder.toString()));
+    }
+
+    @AfterExperiment
+    protected void tearDown() {
+        builder.clear();
+        builder = null;
+    }
+
+    @Benchmark
+    public void timeGetSpans(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            builder.getSpans(0, builder.length(), clazz);
+        }
+    }
+
+    //contains 0 ImageSpans, 2 ParagraphSpans, 53 CharacterStyleSpans
+    public static String TEST_STRING =
+            "<p><span><a href=\"http://android.com\">some link</a></span></p>\n" +
+            "<h1 style=\"margin: 0.0px 0.0px 10.0px 0.0px; line-height: 64.0px; font: 62.0px " +
+                    "'Helvetica Neue Light'; color: #000000; \"><span>some title</span></h1>\n" +
+            "<p><span>by <a href=\"http://android.com\"><span>some name</span></a>\n" +
+            "  <a href=\"https://android.com\"><span>some text</span></a></span></p>\n" +
+            "<p><span>some date</span></p>\n" +
+            "<table cellspacing=\"0\" cellpadding=\"0\">\n" +
+            "  <tbody><tr><td valign=\"bottom\">\n" +
+            "        <p><span><blockquote>a paragraph</blockquote></span><br></p>\n" +
+            "  </tbody></tr></td>\n" +
+            "</table>\n" +
+            "<h2 style=\"margin: 0.0px 0.0px 0.0px 0.0px; line-height: 38.0px; font: 26.0px " +
+                    "'Helvetica Neue Light'; color: #262626; -webkit-text-stroke: #262626\">" +
+                    "<span>some header two</span></h2>\n" +
+            "<p><span>Lorem ipsum dolor concludaturque. </span></p>\n" +
+            "<p><span></span><br></p>\n" +
+            "<p><span>Vix te doctus</span></p>\n" +
+            "<p><span><b>Error mel</b></span><span>, est ei. <a href=\"http://andorid.com\">" +
+                    "<span>asda</span></a> ullamcorper eam.</span></p>\n" +
+            "<p><span>adversarium <a href=\"http://android.com\"><span>efficiantur</span></a>, " +
+                    "mea te.</span></p>\n" +
+            "<p><span></span><br></p>\n" +
+            "<h1>Testing display of HTML elements</h1>\n" +
+            "<h2>2nd level heading</h2>\n" +
+            "<p>test paragraph.</p>\n" +
+            "<h3>3rd level heading</h3>\n" +
+            "<p>test paragraph.</p>\n" +
+            "<h4>4th level heading</h4>\n" +
+            "<p>test paragraph.</p>\n" +
+            "<h5>5th level heading</h5>\n" +
+            "<p>test paragraph.</p>\n" +
+            "<h6>6th level heading</h6>\n" +
+            "<p>test paragraph.</p>\n" +
+            "<h2>level elements</h2>\n" +
+            "<p>a normap paragraph(<code>p</code> element).\n" +
+            "  with some <strong>strong</strong>.</p>\n" +
+            "<div>This is a <code>div</code> element. </div>\n" +
+            "<blockquote><p>This is a block quotation with some <em>style</em></p></blockquote>\n" +
+            "<address>an address element</address>\n" +
+            "<h2>Text-level markup</h2>\n" +
+            "<ul>\n" +
+            "  <li> <abbr title=\"Cascading Style Sheets\">CSS</abbr> (an abbreviation;\n" +
+            "    <code>abbr</code> markup used)\n" +
+            "  <li> <acronym title=\"radio detecting and ranging\">radar</acronym>\n" +
+            "  <li> <b>bolded</b>\n" +
+            "  <li> <big>big thing</big>\n" +
+            "  <li> <font size=6>large size</font>\n" +
+            "  <li> <font face=Courier>Courier font</font>\n" +
+            "  <li> <font color=red>red text</font>\n" +
+            "  <li> <cite>Origin of Species</cite>\n" +
+            "  <li> <code>a[i] = b[i] + c[i);</code>\n" +
+            "  <li> some <del>deleted</del> text\n" +
+            "  <li> an <dfn>octet</dfn> is an\n" +
+            "  <li> this is <em>very</em> simple\n" +
+            "  <li> <i lang=\"la\">Homo sapiens</i>\n" +
+            "  <li> some <ins>inserted</ins> text\n" +
+            "  <li> type <kbd>yes</kbd> when\n" +
+            "  <li> <q>Hello!</q>\n" +
+            "  <li> <q>She said <q>Hello!</q></q>\n" +
+            "  <li> <samp>ccc</samp>\n" +
+            "  <li> <small>important</small>\n" +
+            "  <li> <strike>overstruck</strike>\n" +
+            "  <li> <strong>this is highlighted text</strong>\n" +
+            "  <li> <code>sub</code> and\n" +
+            "    <code>sup</code> x<sub>1</sub> and H<sub>2</sub>O\n" +
+            "    M<sup>lle</sup>, 1<sup>st</sup>, e<sup>x</sup>, sin<sup>2</sup> <i>x</i>,\n" +
+            "    e<sup>x<sup>2</sup></sup> and f(x)<sup>g(x)<sup>a+b+c</sup></sup>\n" +
+            "    (where 2 and a+b+c should appear as exponents of exponents).\n" +
+            "  <li> <tt>text in monospace font</tt>\n" +
+            "  <li> <u>underlined</u> text\n" +
+            "  <li> <code>cat</code> <var>filename</var> displays the\n" +
+            "    the <var>filename</var>.\n" +
+            "</ul>\n";
+
+}
diff --git a/core/tests/benchmarks/src/android/text/SpannableStringInternalCopyBenchmark.java b/core/tests/benchmarks/src/android/text/SpannableStringInternalCopyBenchmark.java
new file mode 100644 (file)
index 0000000..dc5fed0
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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 android.text;
+
+import com.google.caliper.AfterExperiment;
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
+import com.google.caliper.Param;
+
+public class SpannableStringInternalCopyBenchmark {
+
+    @Param({"1", "4", "16"})
+    private String paramStringMult;
+
+    private SpannedString spanned;
+
+    @BeforeExperiment
+    protected void setUp() throws Exception {
+        int strSize = Integer.valueOf(paramStringMult);
+        StringBuilder strBuilder = new StringBuilder();
+        for (int i = 0; i < strSize; i++) {
+            strBuilder.append(SpannableStringBuilderBenchmark.TEST_STRING);
+        }
+        Spanned source = Html.fromHtml(strBuilder.toString());
+        spanned = new SpannedString(source);
+    }
+
+    @AfterExperiment
+    protected void tearDown() {
+        spanned = null;
+    }
+
+    @Benchmark
+    public void timeCopyConstructor(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            new SpannedString(spanned);
+        }
+    }
+
+    @Benchmark
+    public void timeSubsequence(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            spanned.subSequence(1, spanned.length()-1);
+        }
+    }
+
+}