Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.text;
     18 
     19 import android.annotation.Nullable;
     20 import android.graphics.Canvas;
     21 import android.graphics.Paint;
     22 import android.util.Log;
     23 
     24 import com.android.internal.util.ArrayUtils;
     25 import com.android.internal.util.GrowingArrayUtils;
     26 
     27 import libcore.util.EmptyArray;
     28 
     29 import java.lang.reflect.Array;
     30 import java.util.IdentityHashMap;
     31 
     32 /**
     33  * This is the class for text whose content and markup can both be changed.
     34  */
     35 public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
     36         Appendable, GraphicsOperations {
     37     private final static String TAG = "SpannableStringBuilder";
     38     /**
     39      * Create a new SpannableStringBuilder with empty contents
     40      */
     41     public SpannableStringBuilder() {
     42         this("");
     43     }
     44 
     45     /**
     46      * Create a new SpannableStringBuilder containing a copy of the
     47      * specified text, including its spans if any.
     48      */
     49     public SpannableStringBuilder(CharSequence text) {
     50         this(text, 0, text.length());
     51     }
     52 
     53     /**
     54      * Create a new SpannableStringBuilder containing a copy of the
     55      * specified slice of the specified text, including its spans if any.
     56      */
     57     public SpannableStringBuilder(CharSequence text, int start, int end) {
     58         int srclen = end - start;
     59 
     60         if (srclen < 0) throw new StringIndexOutOfBoundsException();
     61 
     62         mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));
     63         mGapStart = srclen;
     64         mGapLength = mText.length - srclen;
     65 
     66         TextUtils.getChars(text, start, end, mText, 0);
     67 
     68         mSpanCount = 0;
     69         mSpanInsertCount = 0;
     70         mSpans = EmptyArray.OBJECT;
     71         mSpanStarts = EmptyArray.INT;
     72         mSpanEnds = EmptyArray.INT;
     73         mSpanFlags = EmptyArray.INT;
     74         mSpanMax = EmptyArray.INT;
     75         mSpanOrder = EmptyArray.INT;
     76         mPrioSortBuffer = EmptyArray.INT;
     77         mOrderSortBuffer = EmptyArray.INT;
     78 
     79         if (text instanceof Spanned) {
     80             Spanned sp = (Spanned) text;
     81             Object[] spans = sp.getSpans(start, end, Object.class);
     82 
     83             for (int i = 0; i < spans.length; i++) {
     84                 if (spans[i] instanceof NoCopySpan) {
     85                     continue;
     86                 }
     87 
     88                 int st = sp.getSpanStart(spans[i]) - start;
     89                 int en = sp.getSpanEnd(spans[i]) - start;
     90                 int fl = sp.getSpanFlags(spans[i]);
     91 
     92                 if (st < 0)
     93                     st = 0;
     94                 if (st > end - start)
     95                     st = end - start;
     96 
     97                 if (en < 0)
     98                     en = 0;
     99                 if (en > end - start)
    100                     en = end - start;
    101 
    102                 setSpan(false, spans[i], st, en, fl);
    103             }
    104             restoreInvariants();
    105         }
    106     }
    107 
    108     public static SpannableStringBuilder valueOf(CharSequence source) {
    109         if (source instanceof SpannableStringBuilder) {
    110             return (SpannableStringBuilder) source;
    111         } else {
    112             return new SpannableStringBuilder(source);
    113         }
    114     }
    115 
    116     /**
    117      * Return the char at the specified offset within the buffer.
    118      */
    119     public char charAt(int where) {
    120         int len = length();
    121         if (where < 0) {
    122             throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
    123         } else if (where >= len) {
    124             throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
    125         }
    126 
    127         if (where >= mGapStart)
    128             return mText[where + mGapLength];
    129         else
    130             return mText[where];
    131     }
    132 
    133     /**
    134      * Return the number of chars in the buffer.
    135      */
    136     public int length() {
    137         return mText.length - mGapLength;
    138     }
    139 
    140     private void resizeFor(int size) {
    141         final int oldLength = mText.length;
    142         if (size + 1 <= oldLength) {
    143             return;
    144         }
    145 
    146         char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size));
    147         System.arraycopy(mText, 0, newText, 0, mGapStart);
    148         final int newLength = newText.length;
    149         final int delta = newLength - oldLength;
    150         final int after = oldLength - (mGapStart + mGapLength);
    151         System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
    152         mText = newText;
    153 
    154         mGapLength += delta;
    155         if (mGapLength < 1)
    156             new Exception("mGapLength < 1").printStackTrace();
    157 
    158         if (mSpanCount != 0) {
    159             for (int i = 0; i < mSpanCount; i++) {
    160                 if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
    161                 if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
    162             }
    163             calcMax(treeRoot());
    164         }
    165     }
    166 
    167     private void moveGapTo(int where) {
    168         if (where == mGapStart)
    169             return;
    170 
    171         boolean atEnd = (where == length());
    172 
    173         if (where < mGapStart) {
    174             int overlap = mGapStart - where;
    175             System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
    176         } else /* where > mGapStart */ {
    177             int overlap = where - mGapStart;
    178             System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
    179         }
    180 
    181         // TODO: be more clever (although the win really isn't that big)
    182         if (mSpanCount != 0) {
    183             for (int i = 0; i < mSpanCount; i++) {
    184                 int start = mSpanStarts[i];
    185                 int end = mSpanEnds[i];
    186 
    187                 if (start > mGapStart)
    188                     start -= mGapLength;
    189                 if (start > where)
    190                     start += mGapLength;
    191                 else if (start == where) {
    192                     int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
    193 
    194                     if (flag == POINT || (atEnd && flag == PARAGRAPH))
    195                         start += mGapLength;
    196                 }
    197 
    198                 if (end > mGapStart)
    199                     end -= mGapLength;
    200                 if (end > where)
    201                     end += mGapLength;
    202                 else if (end == where) {
    203                     int flag = (mSpanFlags[i] & END_MASK);
    204 
    205                     if (flag == POINT || (atEnd && flag == PARAGRAPH))
    206                         end += mGapLength;
    207                 }
    208 
    209                 mSpanStarts[i] = start;
    210                 mSpanEnds[i] = end;
    211             }
    212             calcMax(treeRoot());
    213         }
    214 
    215         mGapStart = where;
    216     }
    217 
    218     // Documentation from interface
    219     public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
    220         return replace(where, where, tb, start, end);
    221     }
    222 
    223     // Documentation from interface
    224     public SpannableStringBuilder insert(int where, CharSequence tb) {
    225         return replace(where, where, tb, 0, tb.length());
    226     }
    227 
    228     // Documentation from interface
    229     public SpannableStringBuilder delete(int start, int end) {
    230         SpannableStringBuilder ret = replace(start, end, "", 0, 0);
    231 
    232         if (mGapLength > 2 * length())
    233             resizeFor(length());
    234 
    235         return ret; // == this
    236     }
    237 
    238     // Documentation from interface
    239     public void clear() {
    240         replace(0, length(), "", 0, 0);
    241         mSpanInsertCount = 0;
    242     }
    243 
    244     // Documentation from interface
    245     public void clearSpans() {
    246         for (int i = mSpanCount - 1; i >= 0; i--) {
    247             Object what = mSpans[i];
    248             int ostart = mSpanStarts[i];
    249             int oend = mSpanEnds[i];
    250 
    251             if (ostart > mGapStart)
    252                 ostart -= mGapLength;
    253             if (oend > mGapStart)
    254                 oend -= mGapLength;
    255 
    256             mSpanCount = i;
    257             mSpans[i] = null;
    258 
    259             sendSpanRemoved(what, ostart, oend);
    260         }
    261         if (mIndexOfSpan != null) {
    262             mIndexOfSpan.clear();
    263         }
    264         mSpanInsertCount = 0;
    265     }
    266 
    267     // Documentation from interface
    268     public SpannableStringBuilder append(CharSequence text) {
    269         int length = length();
    270         return replace(length, length, text, 0, text.length());
    271     }
    272 
    273     /**
    274      * Appends the character sequence {@code text} and spans {@code what} over the appended part.
    275      * See {@link Spanned} for an explanation of what the flags mean.
    276      * @param text the character sequence to append.
    277      * @param what the object to be spanned over the appended text.
    278      * @param flags see {@link Spanned}.
    279      * @return this {@code SpannableStringBuilder}.
    280      */
    281     public SpannableStringBuilder append(CharSequence text, Object what, int flags) {
    282         int start = length();
    283         append(text);
    284         setSpan(what, start, length(), flags);
    285         return this;
    286     }
    287 
    288     // Documentation from interface
    289     public SpannableStringBuilder append(CharSequence text, int start, int end) {
    290         int length = length();
    291         return replace(length, length, text, start, end);
    292     }
    293 
    294     // Documentation from interface
    295     public SpannableStringBuilder append(char text) {
    296         return append(String.valueOf(text));
    297     }
    298 
    299     // Returns true if a node was removed (so we can restart search from root)
    300     private boolean removeSpansForChange(int start, int end, boolean textIsRemoved, int i) {
    301         if ((i & 1) != 0) {
    302             // internal tree node
    303             if (resolveGap(mSpanMax[i]) >= start &&
    304                     removeSpansForChange(start, end, textIsRemoved, leftChild(i))) {
    305                 return true;
    306             }
    307         }
    308         if (i < mSpanCount) {
    309             if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
    310                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
    311                     mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
    312                     mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
    313                     // The following condition indicates that the span would become empty
    314                     (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
    315                 mIndexOfSpan.remove(mSpans[i]);
    316                 removeSpan(i);
    317                 return true;
    318             }
    319             return resolveGap(mSpanStarts[i]) <= end && (i & 1) != 0 &&
    320                 removeSpansForChange(start, end, textIsRemoved, rightChild(i));
    321         }
    322         return false;
    323     }
    324 
    325     private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
    326         // Can be negative
    327         final int replacedLength = end - start;
    328         final int replacementLength = csEnd - csStart;
    329         final int nbNewChars = replacementLength - replacedLength;
    330 
    331         boolean changed = false;
    332         for (int i = mSpanCount - 1; i >= 0; i--) {
    333             int spanStart = mSpanStarts[i];
    334             if (spanStart > mGapStart)
    335                 spanStart -= mGapLength;
    336 
    337             int spanEnd = mSpanEnds[i];
    338             if (spanEnd > mGapStart)
    339                 spanEnd -= mGapLength;
    340 
    341             if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
    342                 int ost = spanStart;
    343                 int oen = spanEnd;
    344                 int clen = length();
    345 
    346                 if (spanStart > start && spanStart <= end) {
    347                     for (spanStart = end; spanStart < clen; spanStart++)
    348                         if (spanStart > end && charAt(spanStart - 1) == '\n')
    349                             break;
    350                 }
    351 
    352                 if (spanEnd > start && spanEnd <= end) {
    353                     for (spanEnd = end; spanEnd < clen; spanEnd++)
    354                         if (spanEnd > end && charAt(spanEnd - 1) == '\n')
    355                             break;
    356                 }
    357 
    358                 if (spanStart != ost || spanEnd != oen) {
    359                     setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
    360                     changed = true;
    361                 }
    362             }
    363 
    364             int flags = 0;
    365             if (spanStart == start) flags |= SPAN_START_AT_START;
    366             else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
    367             if (spanEnd == start) flags |= SPAN_END_AT_START;
    368             else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
    369             mSpanFlags[i] |= flags;
    370         }
    371         if (changed) {
    372             restoreInvariants();
    373         }
    374 
    375         moveGapTo(end);
    376 
    377         if (nbNewChars >= mGapLength) {
    378             resizeFor(mText.length + nbNewChars - mGapLength);
    379         }
    380 
    381         final boolean textIsRemoved = replacementLength == 0;
    382         // The removal pass needs to be done before the gap is updated in order to broadcast the
    383         // correct previous positions to the correct intersecting SpanWatchers
    384         if (replacedLength > 0) { // no need for span fixup on pure insertion
    385             while (mSpanCount > 0 &&
    386                     removeSpansForChange(start, end, textIsRemoved, treeRoot())) {
    387                 // keep deleting spans as needed, and restart from root after every deletion
    388                 // because deletion can invalidate an index.
    389             }
    390         }
    391 
    392         mGapStart += nbNewChars;
    393         mGapLength -= nbNewChars;
    394 
    395         if (mGapLength < 1)
    396             new Exception("mGapLength < 1").printStackTrace();
    397 
    398         TextUtils.getChars(cs, csStart, csEnd, mText, start);
    399 
    400         if (replacedLength > 0) { // no need for span fixup on pure insertion
    401             // TODO potential optimization: only update bounds on intersecting spans
    402             final boolean atEnd = (mGapStart + mGapLength == mText.length);
    403 
    404             for (int i = 0; i < mSpanCount; i++) {
    405                 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
    406                 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
    407                         atEnd, textIsRemoved);
    408 
    409                 final int endFlag = (mSpanFlags[i] & END_MASK);
    410                 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
    411                         atEnd, textIsRemoved);
    412             }
    413             // TODO potential optimization: only fix up invariants when bounds actually changed
    414             restoreInvariants();
    415         }
    416 
    417         if (cs instanceof Spanned) {
    418             Spanned sp = (Spanned) cs;
    419             Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
    420 
    421             for (int i = 0; i < spans.length; i++) {
    422                 int st = sp.getSpanStart(spans[i]);
    423                 int en = sp.getSpanEnd(spans[i]);
    424 
    425                 if (st < csStart) st = csStart;
    426                 if (en > csEnd) en = csEnd;
    427 
    428                 // Add span only if this object is not yet used as a span in this string
    429                 if (getSpanStart(spans[i]) < 0) {
    430                     int copySpanStart = st - csStart + start;
    431                     int copySpanEnd = en - csStart + start;
    432                     int copySpanFlags = sp.getSpanFlags(spans[i]) | SPAN_ADDED;
    433 
    434                     int flagsStart = (copySpanFlags & START_MASK) >> START_SHIFT;
    435                     int flagsEnd = copySpanFlags & END_MASK;
    436 
    437                     if(!isInvalidParagraphStart(copySpanStart, flagsStart) &&
    438                             !isInvalidParagraphEnd(copySpanEnd, flagsEnd)) {
    439                         setSpan(false, spans[i], copySpanStart, copySpanEnd, copySpanFlags);
    440                     }
    441                 }
    442             }
    443             restoreInvariants();
    444         }
    445     }
    446 
    447     private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd,
    448             boolean textIsRemoved) {
    449         if (offset >= start && offset < mGapStart + mGapLength) {
    450             if (flag == POINT) {
    451                 // A POINT located inside the replaced range should be moved to the end of the
    452                 // replaced text.
    453                 // The exception is when the point is at the start of the range and we are doing a
    454                 // text replacement (as opposed to a deletion): the point stays there.
    455                 if (textIsRemoved || offset > start) {
    456                     return mGapStart + mGapLength;
    457                 }
    458             } else {
    459                 if (flag == PARAGRAPH) {
    460                     if (atEnd) {
    461                         return mGapStart + mGapLength;
    462                     }
    463                 } else { // MARK
    464                     // MARKs should be moved to the start, with the exception of a mark located at
    465                     // the end of the range (which will be < mGapStart + mGapLength since mGapLength
    466                     // is > 0, which should stay 'unchanged' at the end of the replaced text.
    467                     if (textIsRemoved || offset < mGapStart - nbNewChars) {
    468                         return start;
    469                     } else {
    470                         // Move to the end of replaced text (needed if nbNewChars != 0)
    471                         return mGapStart;
    472                     }
    473                 }
    474             }
    475         }
    476         return offset;
    477     }
    478 
    479     // Note: caller is responsible for removing the mIndexOfSpan entry.
    480     private void removeSpan(int i) {
    481         Object object = mSpans[i];
    482 
    483         int start = mSpanStarts[i];
    484         int end = mSpanEnds[i];
    485 
    486         if (start > mGapStart) start -= mGapLength;
    487         if (end > mGapStart) end -= mGapLength;
    488 
    489         int count = mSpanCount - (i + 1);
    490         System.arraycopy(mSpans, i + 1, mSpans, i, count);
    491         System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
    492         System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
    493         System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
    494         System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count);
    495 
    496         mSpanCount--;
    497 
    498         invalidateIndex(i);
    499         mSpans[mSpanCount] = null;
    500 
    501         // Invariants must be restored before sending span removed notifications.
    502         restoreInvariants();
    503 
    504         sendSpanRemoved(object, start, end);
    505     }
    506 
    507     // Documentation from interface
    508     public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
    509         return replace(start, end, tb, 0, tb.length());
    510     }
    511 
    512     // Documentation from interface
    513     public SpannableStringBuilder replace(final int start, final int end,
    514             CharSequence tb, int tbstart, int tbend) {
    515         checkRange("replace", start, end);
    516 
    517         int filtercount = mFilters.length;
    518         for (int i = 0; i < filtercount; i++) {
    519             CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
    520 
    521             if (repl != null) {
    522                 tb = repl;
    523                 tbstart = 0;
    524                 tbend = repl.length();
    525             }
    526         }
    527 
    528         final int origLen = end - start;
    529         final int newLen = tbend - tbstart;
    530 
    531         if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
    532             // This is a no-op iif there are no spans in tb that would be added (with a 0-length)
    533             // Early exit so that the text watchers do not get notified
    534             return this;
    535         }
    536 
    537         TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
    538         sendBeforeTextChanged(textWatchers, start, origLen, newLen);
    539 
    540         // Try to keep the cursor / selection at the same relative position during
    541         // a text replacement. If replaced or replacement text length is zero, this
    542         // is already taken care of.
    543         boolean adjustSelection = origLen != 0 && newLen != 0;
    544         int selectionStart = 0;
    545         int selectionEnd = 0;
    546         if (adjustSelection) {
    547             selectionStart = Selection.getSelectionStart(this);
    548             selectionEnd = Selection.getSelectionEnd(this);
    549         }
    550 
    551         change(start, end, tb, tbstart, tbend);
    552 
    553         if (adjustSelection) {
    554             boolean changed = false;
    555             if (selectionStart > start && selectionStart < end) {
    556                 final long diff = selectionStart - start;
    557                 final int offset = Math.toIntExact(diff * newLen / origLen);
    558                 selectionStart = start + offset;
    559 
    560                 changed = true;
    561                 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
    562                         Spanned.SPAN_POINT_POINT);
    563             }
    564             if (selectionEnd > start && selectionEnd < end) {
    565                 final long diff = selectionEnd - start;
    566                 final int offset = Math.toIntExact(diff * newLen / origLen);
    567                 selectionEnd = start + offset;
    568 
    569                 changed = true;
    570                 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
    571                         Spanned.SPAN_POINT_POINT);
    572             }
    573             if (changed) {
    574                 restoreInvariants();
    575             }
    576         }
    577 
    578         sendTextChanged(textWatchers, start, origLen, newLen);
    579         sendAfterTextChanged(textWatchers);
    580 
    581         // Span watchers need to be called after text watchers, which may update the layout
    582         sendToSpanWatchers(start, end, newLen - origLen);
    583 
    584         return this;
    585     }
    586 
    587     private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
    588         if (text instanceof Spanned) {
    589             Spanned spanned = (Spanned) text;
    590             Object[] spans = spanned.getSpans(offset, offset, Object.class);
    591             final int length = spans.length;
    592             for (int i = 0; i < length; i++) {
    593                 Object span = spans[i];
    594                 int flags = spanned.getSpanFlags(span);
    595                 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
    596             }
    597         }
    598         return false;
    599     }
    600 
    601     private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
    602         for (int i = 0; i < mSpanCount; i++) {
    603             int spanFlags = mSpanFlags[i];
    604 
    605             // This loop handles only modified (not added) spans.
    606             if ((spanFlags & SPAN_ADDED) != 0) continue;
    607             int spanStart = mSpanStarts[i];
    608             int spanEnd = mSpanEnds[i];
    609             if (spanStart > mGapStart) spanStart -= mGapLength;
    610             if (spanEnd > mGapStart) spanEnd -= mGapLength;
    611 
    612             int newReplaceEnd = replaceEnd + nbNewChars;
    613             boolean spanChanged = false;
    614 
    615             int previousSpanStart = spanStart;
    616             if (spanStart > newReplaceEnd) {
    617                 if (nbNewChars != 0) {
    618                     previousSpanStart -= nbNewChars;
    619                     spanChanged = true;
    620                 }
    621             } else if (spanStart >= replaceStart) {
    622                 // No change if span start was already at replace interval boundaries before replace
    623                 if ((spanStart != replaceStart ||
    624                         ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
    625                         (spanStart != newReplaceEnd ||
    626                         ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
    627                     // TODO A correct previousSpanStart cannot be computed at this point.
    628                     // It would require to save all the previous spans' positions before the replace
    629                     // Using an invalid -1 value to convey this would break the broacast range
    630                     spanChanged = true;
    631                 }
    632             }
    633 
    634             int previousSpanEnd = spanEnd;
    635             if (spanEnd > newReplaceEnd) {
    636                 if (nbNewChars != 0) {
    637                     previousSpanEnd -= nbNewChars;
    638                     spanChanged = true;
    639                 }
    640             } else if (spanEnd >= replaceStart) {
    641                 // No change if span start was already at replace interval boundaries before replace
    642                 if ((spanEnd != replaceStart ||
    643                         ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
    644                         (spanEnd != newReplaceEnd ||
    645                         ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
    646                     // TODO same as above for previousSpanEnd
    647                     spanChanged = true;
    648                 }
    649             }
    650 
    651             if (spanChanged) {
    652                 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
    653             }
    654             mSpanFlags[i] &= ~SPAN_START_END_MASK;
    655         }
    656 
    657         // Handle added spans
    658         for (int i = 0; i < mSpanCount; i++) {
    659             int spanFlags = mSpanFlags[i];
    660             if ((spanFlags & SPAN_ADDED) != 0) {
    661                 mSpanFlags[i] &= ~SPAN_ADDED;
    662                 int spanStart = mSpanStarts[i];
    663                 int spanEnd = mSpanEnds[i];
    664                 if (spanStart > mGapStart) spanStart -= mGapLength;
    665                 if (spanEnd > mGapStart) spanEnd -= mGapLength;
    666                 sendSpanAdded(mSpans[i], spanStart, spanEnd);
    667             }
    668         }
    669     }
    670 
    671     /**
    672      * Mark the specified range of text with the specified object.
    673      * The flags determine how the span will behave when text is
    674      * inserted at the start or end of the span's range.
    675      */
    676     public void setSpan(Object what, int start, int end, int flags) {
    677         setSpan(true, what, start, end, flags);
    678     }
    679 
    680     // Note: if send is false, then it is the caller's responsibility to restore
    681     // invariants. If send is false and the span already exists, then this method
    682     // will not change the index of any spans.
    683     private void setSpan(boolean send, Object what, int start, int end, int flags) {
    684         checkRange("setSpan", start, end);
    685 
    686         int flagsStart = (flags & START_MASK) >> START_SHIFT;
    687         if(isInvalidParagraphStart(start, flagsStart)) {
    688             throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
    689         }
    690 
    691         int flagsEnd = flags & END_MASK;
    692         if(isInvalidParagraphEnd(end, flagsEnd)) {
    693             throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
    694         }
    695 
    696         // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
    697         if (flagsStart == POINT && flagsEnd == MARK && start == end) {
    698             if (send) {
    699                 Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
    700             }
    701             // Silently ignore invalid spans when they are created from this class.
    702             // This avoids the duplication of the above test code before all the
    703             // calls to setSpan that are done in this class
    704             return;
    705         }
    706 
    707         int nstart = start;
    708         int nend = end;
    709 
    710         if (start > mGapStart) {
    711             start += mGapLength;
    712         } else if (start == mGapStart) {
    713             if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
    714                 start += mGapLength;
    715         }
    716 
    717         if (end > mGapStart) {
    718             end += mGapLength;
    719         } else if (end == mGapStart) {
    720             if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
    721                 end += mGapLength;
    722         }
    723 
    724         if (mIndexOfSpan != null) {
    725             Integer index = mIndexOfSpan.get(what);
    726             if (index != null) {
    727                 int i = index;
    728                 int ostart = mSpanStarts[i];
    729                 int oend = mSpanEnds[i];
    730 
    731                 if (ostart > mGapStart)
    732                     ostart -= mGapLength;
    733                 if (oend > mGapStart)
    734                     oend -= mGapLength;
    735 
    736                 mSpanStarts[i] = start;
    737                 mSpanEnds[i] = end;
    738                 mSpanFlags[i] = flags;
    739 
    740                 if (send) {
    741                     restoreInvariants();
    742                     sendSpanChanged(what, ostart, oend, nstart, nend);
    743                 }
    744 
    745                 return;
    746             }
    747         }
    748 
    749         mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
    750         mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
    751         mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
    752         mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
    753         mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount);
    754         invalidateIndex(mSpanCount);
    755         mSpanCount++;
    756         mSpanInsertCount++;
    757         // Make sure there is enough room for empty interior nodes.
    758         // This magic formula computes the size of the smallest perfect binary
    759         // tree no smaller than mSpanCount.
    760         int sizeOfMax = 2 * treeRoot() + 1;
    761         if (mSpanMax.length < sizeOfMax) {
    762             mSpanMax = new int[sizeOfMax];
    763         }
    764 
    765         if (send) {
    766             restoreInvariants();
    767             sendSpanAdded(what, nstart, nend);
    768         }
    769     }
    770 
    771     private final boolean isInvalidParagraphStart(int start, int flagsStart) {
    772         if (flagsStart == PARAGRAPH) {
    773             if (start != 0 && start != length()) {
    774                 char c = charAt(start - 1);
    775 
    776                 if (c != '\n') return true;
    777             }
    778         }
    779         return false;
    780     }
    781 
    782     private final boolean isInvalidParagraphEnd(int end, int flagsEnd) {
    783         if (flagsEnd == PARAGRAPH) {
    784             if (end != 0 && end != length()) {
    785                 char c = charAt(end - 1);
    786 
    787                 if (c != '\n') return true;
    788             }
    789         }
    790         return false;
    791     }
    792 
    793     /**
    794      * Remove the specified markup object from the buffer.
    795      */
    796     public void removeSpan(Object what) {
    797         if (mIndexOfSpan == null) return;
    798         Integer i = mIndexOfSpan.remove(what);
    799         if (i != null) {
    800             removeSpan(i.intValue());
    801         }
    802     }
    803 
    804     /**
    805      * Return externally visible offset given offset into gapped buffer.
    806      */
    807     private int resolveGap(int i) {
    808         return i > mGapStart ? i - mGapLength : i;
    809     }
    810 
    811     /**
    812      * Return the buffer offset of the beginning of the specified
    813      * markup object, or -1 if it is not attached to this buffer.
    814      */
    815     public int getSpanStart(Object what) {
    816         if (mIndexOfSpan == null) return -1;
    817         Integer i = mIndexOfSpan.get(what);
    818         return i == null ? -1 : resolveGap(mSpanStarts[i]);
    819     }
    820 
    821     /**
    822      * Return the buffer offset of the end of the specified
    823      * markup object, or -1 if it is not attached to this buffer.
    824      */
    825     public int getSpanEnd(Object what) {
    826         if (mIndexOfSpan == null) return -1;
    827         Integer i = mIndexOfSpan.get(what);
    828         return i == null ? -1 : resolveGap(mSpanEnds[i]);
    829     }
    830 
    831     /**
    832      * Return the flags of the end of the specified
    833      * markup object, or 0 if it is not attached to this buffer.
    834      */
    835     public int getSpanFlags(Object what) {
    836         if (mIndexOfSpan == null) return 0;
    837         Integer i = mIndexOfSpan.get(what);
    838         return i == null ? 0 : mSpanFlags[i];
    839     }
    840 
    841     /**
    842      * Return an array of the spans of the specified type that overlap
    843      * the specified range of the buffer.  The kind may be Object.class to get
    844      * a list of all the spans regardless of type.
    845      */
    846     @SuppressWarnings("unchecked")
    847     public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) {
    848         return getSpans(queryStart, queryEnd, kind, true);
    849     }
    850 
    851     /**
    852      * Return an array of the spans of the specified type that overlap
    853      * the specified range of the buffer.  The kind may be Object.class to get
    854      * a list of all the spans regardless of type.
    855      *
    856      * @param queryStart Start index.
    857      * @param queryEnd End index.
    858      * @param kind Class type to search for.
    859      * @param sort If true the results are sorted by the insertion order.
    860      * @param <T>
    861      * @return Array of the spans. Empty array if no results are found.
    862      *
    863      * @hide
    864      */
    865     public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind,
    866                                  boolean sort) {
    867         if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
    868         if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
    869         int count = countSpans(queryStart, queryEnd, kind, treeRoot());
    870         if (count == 0) {
    871             return ArrayUtils.emptyArray(kind);
    872         }
    873 
    874         // Safe conversion, but requires a suppressWarning
    875         T[] ret = (T[]) Array.newInstance(kind, count);
    876         if (sort) {
    877             mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count);
    878             mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count);
    879         }
    880         getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer,
    881                 mOrderSortBuffer, 0, sort);
    882         if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer);
    883         return ret;
    884     }
    885 
    886     private int countSpans(int queryStart, int queryEnd, Class kind, int i) {
    887         int count = 0;
    888         if ((i & 1) != 0) {
    889             // internal tree node
    890             int left = leftChild(i);
    891             int spanMax = mSpanMax[left];
    892             if (spanMax > mGapStart) {
    893                 spanMax -= mGapLength;
    894             }
    895             if (spanMax >= queryStart) {
    896                 count = countSpans(queryStart, queryEnd, kind, left);
    897             }
    898         }
    899         if (i < mSpanCount) {
    900             int spanStart = mSpanStarts[i];
    901             if (spanStart > mGapStart) {
    902                 spanStart -= mGapLength;
    903             }
    904             if (spanStart <= queryEnd) {
    905                 int spanEnd = mSpanEnds[i];
    906                 if (spanEnd > mGapStart) {
    907                     spanEnd -= mGapLength;
    908                 }
    909                 if (spanEnd >= queryStart &&
    910                     (spanStart == spanEnd || queryStart == queryEnd ||
    911                         (spanStart != queryEnd && spanEnd != queryStart)) &&
    912                         (Object.class == kind || kind.isInstance(mSpans[i]))) {
    913                     count++;
    914                 }
    915                 if ((i & 1) != 0) {
    916                     count += countSpans(queryStart, queryEnd, kind, rightChild(i));
    917                 }
    918             }
    919         }
    920         return count;
    921     }
    922 
    923     /**
    924      * Fills the result array with the spans found under the current interval tree node.
    925      *
    926      * @param queryStart Start index for the interval query.
    927      * @param queryEnd End index for the interval query.
    928      * @param kind Class type to search for.
    929      * @param i Index of the current tree node.
    930      * @param ret Array to be filled with results.
    931      * @param priority Buffer to keep record of the priorities of spans found.
    932      * @param insertionOrder Buffer to keep record of the insertion orders of spans found.
    933      * @param count The number of found spans.
    934      * @param sort Flag to fill the priority and insertion order buffers. If false then
    935      *             the spans with priority flag will be sorted in the result array.
    936      * @param <T>
    937      * @return The total number of spans found.
    938      */
    939     @SuppressWarnings("unchecked")
    940     private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind,
    941             int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort) {
    942         if ((i & 1) != 0) {
    943             // internal tree node
    944             int left = leftChild(i);
    945             int spanMax = mSpanMax[left];
    946             if (spanMax > mGapStart) {
    947                 spanMax -= mGapLength;
    948             }
    949             if (spanMax >= queryStart) {
    950                 count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority,
    951                         insertionOrder, count, sort);
    952             }
    953         }
    954         if (i >= mSpanCount) return count;
    955         int spanStart = mSpanStarts[i];
    956         if (spanStart > mGapStart) {
    957             spanStart -= mGapLength;
    958         }
    959         if (spanStart <= queryEnd) {
    960             int spanEnd = mSpanEnds[i];
    961             if (spanEnd > mGapStart) {
    962                 spanEnd -= mGapLength;
    963             }
    964             if (spanEnd >= queryStart &&
    965                     (spanStart == spanEnd || queryStart == queryEnd ||
    966                         (spanStart != queryEnd && spanEnd != queryStart)) &&
    967                         (Object.class == kind || kind.isInstance(mSpans[i]))) {
    968                 int spanPriority = mSpanFlags[i] & SPAN_PRIORITY;
    969                 int target = count;
    970                 if (sort) {
    971                     priority[target] = spanPriority;
    972                     insertionOrder[target] = mSpanOrder[i];
    973                 } else if (spanPriority != 0) {
    974                     //insertion sort for elements with priority
    975                     int j = 0;
    976                     for (; j < count; j++) {
    977                         int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
    978                         if (spanPriority > p) break;
    979                     }
    980                     System.arraycopy(ret, j, ret, j + 1, count - j);
    981                     target = j;
    982                 }
    983                 ret[target] = (T) mSpans[i];
    984                 count++;
    985             }
    986             if (count < ret.length && (i & 1) != 0) {
    987                 count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority,
    988                         insertionOrder, count, sort);
    989             }
    990         }
    991         return count;
    992     }
    993 
    994     /**
    995      * Check the size of the buffer and grow if required.
    996      *
    997      * @param buffer Buffer to be checked.
    998      * @param size Required size.
    999      * @return Same buffer instance if the current size is greater than required size. Otherwise a
   1000      * new instance is created and returned.
   1001      */
   1002     private final int[] checkSortBuffer(int[] buffer, int size) {
   1003         if(size > buffer.length) {
   1004             return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size));
   1005         }
   1006         return buffer;
   1007     }
   1008 
   1009     /**
   1010      * An iterative heap sort implementation. It will sort the spans using first their priority
   1011      * then insertion order. A span with higher priority will be before a span with lower
   1012      * priority. If priorities are the same, the spans will be sorted with insertion order. A
   1013      * span with a lower insertion order will be before a span with a higher insertion order.
   1014      *
   1015      * @param array Span array to be sorted.
   1016      * @param priority Priorities of the spans
   1017      * @param insertionOrder Insertion orders of the spans
   1018      * @param <T> Span object type.
   1019      * @param <T>
   1020      */
   1021     private final <T> void sort(T[] array, int[] priority, int[] insertionOrder) {
   1022         int size = array.length;
   1023         for (int i = size / 2 - 1; i >= 0; i--) {
   1024             siftDown(i, array, size, priority, insertionOrder);
   1025         }
   1026 
   1027         for (int i = size - 1; i > 0; i--) {
   1028             T v = array[0];
   1029             int prio = priority[0];
   1030             int insertOrder = insertionOrder[0];
   1031             array[0] = array[i];
   1032             priority[0] = priority[i];
   1033             insertionOrder[0] = insertionOrder[i];
   1034             siftDown(0, array, i, priority, insertionOrder);
   1035             array[i] = v;
   1036             priority[i] = prio;
   1037             insertionOrder[i] = insertOrder;
   1038         }
   1039     }
   1040 
   1041     /**
   1042      * Helper function for heap sort.
   1043      *
   1044      * @param index Index of the element to sift down.
   1045      * @param array Span array to be sorted.
   1046      * @param size Current heap size.
   1047      * @param priority Priorities of the spans
   1048      * @param insertionOrder Insertion orders of the spans
   1049      * @param <T> Span object type.
   1050      */
   1051     private final <T> void siftDown(int index, T[] array, int size, int[] priority,
   1052                                     int[] insertionOrder) {
   1053         T v = array[index];
   1054         int prio = priority[index];
   1055         int insertOrder = insertionOrder[index];
   1056 
   1057         int left = 2 * index + 1;
   1058         while (left < size) {
   1059             if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) {
   1060                 left++;
   1061             }
   1062             if (compareSpans(index, left, priority, insertionOrder) >= 0) {
   1063                 break;
   1064             }
   1065             array[index] = array[left];
   1066             priority[index] = priority[left];
   1067             insertionOrder[index] = insertionOrder[left];
   1068             index = left;
   1069             left = 2 * index + 1;
   1070         }
   1071         array[index] = v;
   1072         priority[index] = prio;
   1073         insertionOrder[index] = insertOrder;
   1074     }
   1075 
   1076     /**
   1077      * Compare two span elements in an array. Comparison is based first on the priority flag of
   1078      * the span, and then the insertion order of the span.
   1079      *
   1080      * @param left Index of the element to compare.
   1081      * @param right Index of the other element to compare.
   1082      * @param priority Priorities of the spans
   1083      * @param insertionOrder Insertion orders of the spans
   1084      * @return
   1085      */
   1086     private final int compareSpans(int left, int right, int[] priority,
   1087                                        int[] insertionOrder) {
   1088         int priority1 = priority[left];
   1089         int priority2 = priority[right];
   1090         if (priority1 == priority2) {
   1091             return Integer.compare(insertionOrder[left], insertionOrder[right]);
   1092         }
   1093         // since high priority has to be before a lower priority, the arguments to compare are
   1094         // opposite of the insertion order check.
   1095         return Integer.compare(priority2, priority1);
   1096     }
   1097 
   1098     /**
   1099      * Return the next offset after <code>start</code> but less than or
   1100      * equal to <code>limit</code> where a span of the specified type
   1101      * begins or ends.
   1102      */
   1103     public int nextSpanTransition(int start, int limit, Class kind) {
   1104         if (mSpanCount == 0) return limit;
   1105         if (kind == null) {
   1106             kind = Object.class;
   1107         }
   1108         return nextSpanTransitionRec(start, limit, kind, treeRoot());
   1109     }
   1110 
   1111     private int nextSpanTransitionRec(int start, int limit, Class kind, int i) {
   1112         if ((i & 1) != 0) {
   1113             // internal tree node
   1114             int left = leftChild(i);
   1115             if (resolveGap(mSpanMax[left]) > start) {
   1116                 limit = nextSpanTransitionRec(start, limit, kind, left);
   1117             }
   1118         }
   1119         if (i < mSpanCount) {
   1120             int st = resolveGap(mSpanStarts[i]);
   1121             int en = resolveGap(mSpanEnds[i]);
   1122             if (st > start && st < limit && kind.isInstance(mSpans[i]))
   1123                 limit = st;
   1124             if (en > start && en < limit && kind.isInstance(mSpans[i]))
   1125                 limit = en;
   1126             if (st < limit && (i & 1) != 0) {
   1127                 limit = nextSpanTransitionRec(start, limit, kind, rightChild(i));
   1128             }
   1129         }
   1130 
   1131         return limit;
   1132     }
   1133 
   1134     /**
   1135      * Return a new CharSequence containing a copy of the specified
   1136      * range of this buffer, including the overlapping spans.
   1137      */
   1138     public CharSequence subSequence(int start, int end) {
   1139         return new SpannableStringBuilder(this, start, end);
   1140     }
   1141 
   1142     /**
   1143      * Copy the specified range of chars from this buffer into the
   1144      * specified array, beginning at the specified offset.
   1145      */
   1146     public void getChars(int start, int end, char[] dest, int destoff) {
   1147         checkRange("getChars", start, end);
   1148 
   1149         if (end <= mGapStart) {
   1150             System.arraycopy(mText, start, dest, destoff, end - start);
   1151         } else if (start >= mGapStart) {
   1152             System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
   1153         } else {
   1154             System.arraycopy(mText, start, dest, destoff, mGapStart - start);
   1155             System.arraycopy(mText, mGapStart + mGapLength,
   1156                     dest, destoff + (mGapStart - start),
   1157                     end - mGapStart);
   1158         }
   1159     }
   1160 
   1161     /**
   1162      * Return a String containing a copy of the chars in this buffer.
   1163      */
   1164     @Override
   1165     public String toString() {
   1166         int len = length();
   1167         char[] buf = new char[len];
   1168 
   1169         getChars(0, len, buf, 0);
   1170         return new String(buf);
   1171     }
   1172 
   1173     /**
   1174      * Return a String containing a copy of the chars in this buffer, limited to the
   1175      * [start, end[ range.
   1176      * @hide
   1177      */
   1178     public String substring(int start, int end) {
   1179         char[] buf = new char[end - start];
   1180         getChars(start, end, buf, 0);
   1181         return new String(buf);
   1182     }
   1183 
   1184     /**
   1185      * Returns the depth of TextWatcher callbacks. Returns 0 when the object is not handling
   1186      * TextWatchers. A return value greater than 1 implies that a TextWatcher caused a change that
   1187      * recursively triggered a TextWatcher.
   1188      */
   1189     public int getTextWatcherDepth() {
   1190         return mTextWatcherDepth;
   1191     }
   1192 
   1193     private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
   1194         int n = watchers.length;
   1195 
   1196         mTextWatcherDepth++;
   1197         for (int i = 0; i < n; i++) {
   1198             watchers[i].beforeTextChanged(this, start, before, after);
   1199         }
   1200         mTextWatcherDepth--;
   1201     }
   1202 
   1203     private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
   1204         int n = watchers.length;
   1205 
   1206         mTextWatcherDepth++;
   1207         for (int i = 0; i < n; i++) {
   1208             watchers[i].onTextChanged(this, start, before, after);
   1209         }
   1210         mTextWatcherDepth--;
   1211     }
   1212 
   1213     private void sendAfterTextChanged(TextWatcher[] watchers) {
   1214         int n = watchers.length;
   1215 
   1216         mTextWatcherDepth++;
   1217         for (int i = 0; i < n; i++) {
   1218             watchers[i].afterTextChanged(this);
   1219         }
   1220         mTextWatcherDepth--;
   1221     }
   1222 
   1223     private void sendSpanAdded(Object what, int start, int end) {
   1224         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
   1225         int n = recip.length;
   1226 
   1227         for (int i = 0; i < n; i++) {
   1228             recip[i].onSpanAdded(this, what, start, end);
   1229         }
   1230     }
   1231 
   1232     private void sendSpanRemoved(Object what, int start, int end) {
   1233         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
   1234         int n = recip.length;
   1235 
   1236         for (int i = 0; i < n; i++) {
   1237             recip[i].onSpanRemoved(this, what, start, end);
   1238         }
   1239     }
   1240 
   1241     private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
   1242         // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
   1243         // called, so that the order of the span does not affect this broadcast.
   1244         SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
   1245                 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
   1246         int n = spanWatchers.length;
   1247         for (int i = 0; i < n; i++) {
   1248             spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
   1249         }
   1250     }
   1251 
   1252     private static String region(int start, int end) {
   1253         return "(" + start + " ... " + end + ")";
   1254     }
   1255 
   1256     private void checkRange(final String operation, int start, int end) {
   1257         if (end < start) {
   1258             throw new IndexOutOfBoundsException(operation + " " +
   1259                     region(start, end) + " has end before start");
   1260         }
   1261 
   1262         int len = length();
   1263 
   1264         if (start > len || end > len) {
   1265             throw new IndexOutOfBoundsException(operation + " " +
   1266                     region(start, end) + " ends beyond length " + len);
   1267         }
   1268 
   1269         if (start < 0 || end < 0) {
   1270             throw new IndexOutOfBoundsException(operation + " " +
   1271                     region(start, end) + " starts before 0");
   1272         }
   1273     }
   1274 
   1275     /*
   1276     private boolean isprint(char c) { // XXX
   1277         if (c >= ' ' && c <= '~')
   1278             return true;
   1279         else
   1280             return false;
   1281     }
   1282 
   1283     private static final int startFlag(int flag) {
   1284         return (flag >> 4) & 0x0F;
   1285     }
   1286 
   1287     private static final int endFlag(int flag) {
   1288         return flag & 0x0F;
   1289     }
   1290 
   1291     public void dump() { // XXX
   1292         for (int i = 0; i < mGapStart; i++) {
   1293             System.out.print('|');
   1294             System.out.print(' ');
   1295             System.out.print(isprint(mText[i]) ? mText[i] : '.');
   1296             System.out.print(' ');
   1297         }
   1298 
   1299         for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
   1300             System.out.print('|');
   1301             System.out.print('(');
   1302             System.out.print(isprint(mText[i]) ? mText[i] : '.');
   1303             System.out.print(')');
   1304         }
   1305 
   1306         for (int i = mGapStart + mGapLength; i < mText.length; i++) {
   1307             System.out.print('|');
   1308             System.out.print(' ');
   1309             System.out.print(isprint(mText[i]) ? mText[i] : '.');
   1310             System.out.print(' ');
   1311         }
   1312 
   1313         System.out.print('\n');
   1314 
   1315         for (int i = 0; i < mText.length + 1; i++) {
   1316             int found = 0;
   1317             int wfound = 0;
   1318 
   1319             for (int j = 0; j < mSpanCount; j++) {
   1320                 if (mSpanStarts[j] == i) {
   1321                     found = 1;
   1322                     wfound = j;
   1323                     break;
   1324                 }
   1325 
   1326                 if (mSpanEnds[j] == i) {
   1327                     found = 2;
   1328                     wfound = j;
   1329                     break;
   1330                 }
   1331             }
   1332 
   1333             if (found == 1) {
   1334                 if (startFlag(mSpanFlags[wfound]) == MARK)
   1335                     System.out.print("(   ");
   1336                 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
   1337                     System.out.print("<   ");
   1338                 else
   1339                     System.out.print("[   ");
   1340             } else if (found == 2) {
   1341                 if (endFlag(mSpanFlags[wfound]) == POINT)
   1342                     System.out.print(")   ");
   1343                 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
   1344                     System.out.print(">   ");
   1345                 else
   1346                     System.out.print("]   ");
   1347             } else {
   1348                 System.out.print("    ");
   1349             }
   1350         }
   1351 
   1352         System.out.print("\n");
   1353     }
   1354     */
   1355 
   1356     /**
   1357      * Don't call this yourself -- exists for Canvas to use internally.
   1358      * {@hide}
   1359      */
   1360     public void drawText(Canvas c, int start, int end, float x, float y, Paint p) {
   1361         checkRange("drawText", start, end);
   1362 
   1363         if (end <= mGapStart) {
   1364             c.drawText(mText, start, end - start, x, y, p);
   1365         } else if (start >= mGapStart) {
   1366             c.drawText(mText, start + mGapLength, end - start, x, y, p);
   1367         } else {
   1368             char[] buf = TextUtils.obtain(end - start);
   1369 
   1370             getChars(start, end, buf, 0);
   1371             c.drawText(buf, 0, end - start, x, y, p);
   1372             TextUtils.recycle(buf);
   1373         }
   1374     }
   1375 
   1376 
   1377     /**
   1378      * Don't call this yourself -- exists for Canvas to use internally.
   1379      * {@hide}
   1380      */
   1381     public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
   1382             float x, float y, boolean isRtl, Paint p) {
   1383         checkRange("drawTextRun", start, end);
   1384 
   1385         int contextLen = contextEnd - contextStart;
   1386         int len = end - start;
   1387         if (contextEnd <= mGapStart) {
   1388             c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p);
   1389         } else if (contextStart >= mGapStart) {
   1390             c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
   1391                     contextLen, x, y, isRtl, p);
   1392         } else {
   1393             char[] buf = TextUtils.obtain(contextLen);
   1394             getChars(contextStart, contextEnd, buf, 0);
   1395             c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p);
   1396             TextUtils.recycle(buf);
   1397         }
   1398     }
   1399 
   1400     /**
   1401      * Don't call this yourself -- exists for Paint to use internally.
   1402      * {@hide}
   1403      */
   1404     public float measureText(int start, int end, Paint p) {
   1405         checkRange("measureText", start, end);
   1406 
   1407         float ret;
   1408 
   1409         if (end <= mGapStart) {
   1410             ret = p.measureText(mText, start, end - start);
   1411         } else if (start >= mGapStart) {
   1412             ret = p.measureText(mText, start + mGapLength, end - start);
   1413         } else {
   1414             char[] buf = TextUtils.obtain(end - start);
   1415 
   1416             getChars(start, end, buf, 0);
   1417             ret = p.measureText(buf, 0, end - start);
   1418             TextUtils.recycle(buf);
   1419         }
   1420 
   1421         return ret;
   1422     }
   1423 
   1424     /**
   1425      * Don't call this yourself -- exists for Paint to use internally.
   1426      * {@hide}
   1427      */
   1428     public int getTextWidths(int start, int end, float[] widths, Paint p) {
   1429         checkRange("getTextWidths", start, end);
   1430 
   1431         int ret;
   1432 
   1433         if (end <= mGapStart) {
   1434             ret = p.getTextWidths(mText, start, end - start, widths);
   1435         } else if (start >= mGapStart) {
   1436             ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
   1437         } else {
   1438             char[] buf = TextUtils.obtain(end - start);
   1439 
   1440             getChars(start, end, buf, 0);
   1441             ret = p.getTextWidths(buf, 0, end - start, widths);
   1442             TextUtils.recycle(buf);
   1443         }
   1444 
   1445         return ret;
   1446     }
   1447 
   1448     /**
   1449      * Don't call this yourself -- exists for Paint to use internally.
   1450      * {@hide}
   1451      */
   1452     public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl,
   1453             float[] advances, int advancesPos, Paint p) {
   1454 
   1455         float ret;
   1456 
   1457         int contextLen = contextEnd - contextStart;
   1458         int len = end - start;
   1459 
   1460         if (end <= mGapStart) {
   1461             ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
   1462                     isRtl, advances, advancesPos);
   1463         } else if (start >= mGapStart) {
   1464             ret = p.getTextRunAdvances(mText, start + mGapLength, len,
   1465                     contextStart + mGapLength, contextLen, isRtl, advances, advancesPos);
   1466         } else {
   1467             char[] buf = TextUtils.obtain(contextLen);
   1468             getChars(contextStart, contextEnd, buf, 0);
   1469             ret = p.getTextRunAdvances(buf, start - contextStart, len,
   1470                     0, contextLen, isRtl, advances, advancesPos);
   1471             TextUtils.recycle(buf);
   1472         }
   1473 
   1474         return ret;
   1475     }
   1476 
   1477     /**
   1478      * Returns the next cursor position in the run.  This avoids placing the cursor between
   1479      * surrogates, between characters that form conjuncts, between base characters and combining
   1480      * marks, or within a reordering cluster.
   1481      *
   1482      * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
   1483      * span enclosing the cursor in the direction of movement.
   1484      * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
   1485      * the start of the string.</p>
   1486      *
   1487      * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
   1488      * this returns -1.  Otherwise this will never return a value before contextStart or after
   1489      * contextEnd.</p>
   1490      *
   1491      * @param contextStart the start index of the context
   1492      * @param contextEnd the (non-inclusive) end index of the context
   1493      * @param dir either DIRECTION_RTL or DIRECTION_LTR
   1494      * @param offset the cursor position to move from
   1495      * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
   1496      * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
   1497      * CURSOR_AT_OR_BEFORE, or CURSOR_AT
   1498      * @param p the Paint object that is requesting this information
   1499      * @return the offset of the next position, or -1
   1500      * @deprecated This is an internal method, refrain from using it in your code
   1501      */
   1502     @Deprecated
   1503     public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset,
   1504             int cursorOpt, Paint p) {
   1505 
   1506         int ret;
   1507 
   1508         int contextLen = contextEnd - contextStart;
   1509         if (contextEnd <= mGapStart) {
   1510             ret = p.getTextRunCursor(mText, contextStart, contextLen,
   1511                     dir, offset, cursorOpt);
   1512         } else if (contextStart >= mGapStart) {
   1513             ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
   1514                     dir, offset + mGapLength, cursorOpt) - mGapLength;
   1515         } else {
   1516             char[] buf = TextUtils.obtain(contextLen);
   1517             getChars(contextStart, contextEnd, buf, 0);
   1518             ret = p.getTextRunCursor(buf, 0, contextLen,
   1519                     dir, offset - contextStart, cursorOpt) + contextStart;
   1520             TextUtils.recycle(buf);
   1521         }
   1522 
   1523         return ret;
   1524     }
   1525 
   1526     // Documentation from interface
   1527     public void setFilters(InputFilter[] filters) {
   1528         if (filters == null) {
   1529             throw new IllegalArgumentException();
   1530         }
   1531 
   1532         mFilters = filters;
   1533     }
   1534 
   1535     // Documentation from interface
   1536     public InputFilter[] getFilters() {
   1537         return mFilters;
   1538     }
   1539 
   1540     // Same as SpannableStringInternal
   1541     @Override
   1542     public boolean equals(Object o) {
   1543         if (o instanceof Spanned &&
   1544                 toString().equals(o.toString())) {
   1545             Spanned other = (Spanned) o;
   1546             // Check span data
   1547             Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
   1548             if (mSpanCount == otherSpans.length) {
   1549                 for (int i = 0; i < mSpanCount; ++i) {
   1550                     Object thisSpan = mSpans[i];
   1551                     Object otherSpan = otherSpans[i];
   1552                     if (thisSpan == this) {
   1553                         if (other != otherSpan ||
   1554                                 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
   1555                                 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
   1556                                 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
   1557                             return false;
   1558                         }
   1559                     } else if (!thisSpan.equals(otherSpan) ||
   1560                             getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
   1561                             getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
   1562                             getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
   1563                         return false;
   1564                     }
   1565                 }
   1566                 return true;
   1567             }
   1568         }
   1569         return false;
   1570     }
   1571 
   1572     // Same as SpannableStringInternal
   1573     @Override
   1574     public int hashCode() {
   1575         int hash = toString().hashCode();
   1576         hash = hash * 31 + mSpanCount;
   1577         for (int i = 0; i < mSpanCount; ++i) {
   1578             Object span = mSpans[i];
   1579             if (span != this) {
   1580                 hash = hash * 31 + span.hashCode();
   1581             }
   1582             hash = hash * 31 + getSpanStart(span);
   1583             hash = hash * 31 + getSpanEnd(span);
   1584             hash = hash * 31 + getSpanFlags(span);
   1585         }
   1586         return hash;
   1587     }
   1588 
   1589     // Primitives for treating span list as binary tree
   1590 
   1591     // The spans (along with start and end offsets and flags) are stored in linear arrays sorted
   1592     // by start offset. For fast searching, there is a binary search structure imposed over these
   1593     // arrays. This structure is inorder traversal of a perfect binary tree, a slightly unusual
   1594     // but advantageous approach.
   1595 
   1596     // The value-containing nodes are indexed 0 <= i < n (where n = mSpanCount), thus preserving
   1597     // logic that accesses the values as a contiguous array. Other balanced binary tree approaches
   1598     // (such as a complete binary tree) would require some shuffling of node indices.
   1599 
   1600     // Basic properties of this structure: For a perfect binary tree of height m:
   1601     // The tree has 2^(m+1) - 1 total nodes.
   1602     // The root of the tree has index 2^m - 1.
   1603     // All leaf nodes have even index, all interior nodes odd.
   1604     // The height of a node of index i is the number of trailing ones in i's binary representation.
   1605     // The left child of a node i of height h is i - 2^(h - 1).
   1606     // The right child of a node i of height h is i + 2^(h - 1).
   1607 
   1608     // Note that for arbitrary n, interior nodes of this tree may be >= n. Thus, the general
   1609     // structure of a recursive traversal of node i is:
   1610     // * traverse left child if i is an interior node
   1611     // * process i if i < n
   1612     // * traverse right child if i is an interior node and i < n
   1613 
   1614     private int treeRoot() {
   1615         return Integer.highestOneBit(mSpanCount) - 1;
   1616     }
   1617 
   1618     // (i+1) & ~i is equal to 2^(the number of trailing ones in i)
   1619     private static int leftChild(int i) {
   1620         return i - (((i + 1) & ~i) >> 1);
   1621     }
   1622 
   1623     private static int rightChild(int i) {
   1624         return i + (((i + 1) & ~i) >> 1);
   1625     }
   1626 
   1627     // The span arrays are also augmented by an mSpanMax[] array that represents an interval tree
   1628     // over the binary tree structure described above. For each node, the mSpanMax[] array contains
   1629     // the maximum value of mSpanEnds of that node and its descendants. Thus, traversals can
   1630     // easily reject subtrees that contain no spans overlapping the area of interest.
   1631 
   1632     // Note that mSpanMax[] also has a valid valuefor interior nodes of index >= n, but which have
   1633     // descendants of index < n. In these cases, it simply represents the maximum span end of its
   1634     // descendants. This is a consequence of the perfect binary tree structure.
   1635     private int calcMax(int i) {
   1636         int max = 0;
   1637         if ((i & 1) != 0) {
   1638             // internal tree node
   1639             max = calcMax(leftChild(i));
   1640         }
   1641         if (i < mSpanCount) {
   1642             max = Math.max(max, mSpanEnds[i]);
   1643             if ((i & 1) != 0) {
   1644                 max = Math.max(max, calcMax(rightChild(i)));
   1645             }
   1646         }
   1647         mSpanMax[i] = max;
   1648         return max;
   1649     }
   1650 
   1651     // restores binary interval tree invariants after any mutation of span structure
   1652     private void restoreInvariants() {
   1653         if (mSpanCount == 0) return;
   1654 
   1655         // invariant 1: span starts are nondecreasing
   1656 
   1657         // This is a simple insertion sort because we expect it to be mostly sorted.
   1658         for (int i = 1; i < mSpanCount; i++) {
   1659             if (mSpanStarts[i] < mSpanStarts[i - 1]) {
   1660                 Object span = mSpans[i];
   1661                 int start = mSpanStarts[i];
   1662                 int end = mSpanEnds[i];
   1663                 int flags = mSpanFlags[i];
   1664                 int insertionOrder = mSpanOrder[i];
   1665                 int j = i;
   1666                 do {
   1667                     mSpans[j] = mSpans[j - 1];
   1668                     mSpanStarts[j] = mSpanStarts[j - 1];
   1669                     mSpanEnds[j] = mSpanEnds[j - 1];
   1670                     mSpanFlags[j] = mSpanFlags[j - 1];
   1671                     mSpanOrder[j] = mSpanOrder[j - 1];
   1672                     j--;
   1673                 } while (j > 0 && start < mSpanStarts[j - 1]);
   1674                 mSpans[j] = span;
   1675                 mSpanStarts[j] = start;
   1676                 mSpanEnds[j] = end;
   1677                 mSpanFlags[j] = flags;
   1678                 mSpanOrder[j] = insertionOrder;
   1679                 invalidateIndex(j);
   1680             }
   1681         }
   1682 
   1683         // invariant 2: max is max span end for each node and its descendants
   1684         calcMax(treeRoot());
   1685 
   1686         // invariant 3: mIndexOfSpan maps spans back to indices
   1687         if (mIndexOfSpan == null) {
   1688             mIndexOfSpan = new IdentityHashMap<Object, Integer>();
   1689         }
   1690         for (int i = mLowWaterMark; i < mSpanCount; i++) {
   1691             Integer existing = mIndexOfSpan.get(mSpans[i]);
   1692             if (existing == null || existing != i) {
   1693                 mIndexOfSpan.put(mSpans[i], i);
   1694             }
   1695         }
   1696         mLowWaterMark = Integer.MAX_VALUE;
   1697     }
   1698 
   1699     // Call this on any update to mSpans[], so that mIndexOfSpan can be updated
   1700     private void invalidateIndex(int i) {
   1701         mLowWaterMark = Math.min(i, mLowWaterMark);
   1702     }
   1703 
   1704     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
   1705     private InputFilter[] mFilters = NO_FILTERS;
   1706 
   1707     private char[] mText;
   1708     private int mGapStart;
   1709     private int mGapLength;
   1710 
   1711     private Object[] mSpans;
   1712     private int[] mSpanStarts;
   1713     private int[] mSpanEnds;
   1714     private int[] mSpanMax;  // see calcMax() for an explanation of what this array stores
   1715     private int[] mSpanFlags;
   1716     private int[] mSpanOrder;  // store the order of span insertion
   1717     private int mSpanInsertCount;  // counter for the span insertion
   1718     private int[] mPrioSortBuffer;  // buffer used to sort getSpans result
   1719     private int[] mOrderSortBuffer;  // buffer used to sort getSpans result
   1720 
   1721     private int mSpanCount;
   1722     private IdentityHashMap<Object, Integer> mIndexOfSpan;
   1723     private int mLowWaterMark;  // indices below this have not been touched
   1724 
   1725     // TextWatcher callbacks may trigger changes that trigger more callbacks. This keeps track of
   1726     // how deep the callbacks go.
   1727     private int mTextWatcherDepth;
   1728 
   1729     // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
   1730     private static final int MARK = 1;
   1731     private static final int POINT = 2;
   1732     private static final int PARAGRAPH = 3;
   1733 
   1734     private static final int START_MASK = 0xF0;
   1735     private static final int END_MASK = 0x0F;
   1736     private static final int START_SHIFT = 4;
   1737 
   1738     // These bits are not (currently) used by SPANNED flags
   1739     private static final int SPAN_ADDED = 0x800;
   1740     private static final int SPAN_START_AT_START = 0x1000;
   1741     private static final int SPAN_START_AT_END = 0x2000;
   1742     private static final int SPAN_END_AT_START = 0x4000;
   1743     private static final int SPAN_END_AT_END = 0x8000;
   1744     private static final int SPAN_START_END_MASK = 0xF000;
   1745 }
   1746