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.graphics.Canvas;
     20 import android.graphics.Paint;
     21 import android.util.Log;
     22 
     23 import com.android.internal.util.ArrayUtils;
     24 import com.android.internal.util.GrowingArrayUtils;
     25 
     26 import libcore.util.EmptyArray;
     27 
     28 import java.lang.reflect.Array;
     29 
     30 /**
     31  * This is the class for text whose content and markup can both be changed.
     32  */
     33 public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
     34         Appendable, GraphicsOperations {
     35     private final static String TAG = "SpannableStringBuilder";
     36     /**
     37      * Create a new SpannableStringBuilder with empty contents
     38      */
     39     public SpannableStringBuilder() {
     40         this("");
     41     }
     42 
     43     /**
     44      * Create a new SpannableStringBuilder containing a copy of the
     45      * specified text, including its spans if any.
     46      */
     47     public SpannableStringBuilder(CharSequence text) {
     48         this(text, 0, text.length());
     49     }
     50 
     51     /**
     52      * Create a new SpannableStringBuilder containing a copy of the
     53      * specified slice of the specified text, including its spans if any.
     54      */
     55     public SpannableStringBuilder(CharSequence text, int start, int end) {
     56         int srclen = end - start;
     57 
     58         if (srclen < 0) throw new StringIndexOutOfBoundsException();
     59 
     60         mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));
     61         mGapStart = srclen;
     62         mGapLength = mText.length - srclen;
     63 
     64         TextUtils.getChars(text, start, end, mText, 0);
     65 
     66         mSpanCount = 0;
     67         mSpans = EmptyArray.OBJECT;
     68         mSpanStarts = EmptyArray.INT;
     69         mSpanEnds = EmptyArray.INT;
     70         mSpanFlags = EmptyArray.INT;
     71 
     72         if (text instanceof Spanned) {
     73             Spanned sp = (Spanned) text;
     74             Object[] spans = sp.getSpans(start, end, Object.class);
     75 
     76             for (int i = 0; i < spans.length; i++) {
     77                 if (spans[i] instanceof NoCopySpan) {
     78                     continue;
     79                 }
     80 
     81                 int st = sp.getSpanStart(spans[i]) - start;
     82                 int en = sp.getSpanEnd(spans[i]) - start;
     83                 int fl = sp.getSpanFlags(spans[i]);
     84 
     85                 if (st < 0)
     86                     st = 0;
     87                 if (st > end - start)
     88                     st = end - start;
     89 
     90                 if (en < 0)
     91                     en = 0;
     92                 if (en > end - start)
     93                     en = end - start;
     94 
     95                 setSpan(false, spans[i], st, en, fl);
     96             }
     97         }
     98     }
     99 
    100     public static SpannableStringBuilder valueOf(CharSequence source) {
    101         if (source instanceof SpannableStringBuilder) {
    102             return (SpannableStringBuilder) source;
    103         } else {
    104             return new SpannableStringBuilder(source);
    105         }
    106     }
    107 
    108     /**
    109      * Return the char at the specified offset within the buffer.
    110      */
    111     public char charAt(int where) {
    112         int len = length();
    113         if (where < 0) {
    114             throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
    115         } else if (where >= len) {
    116             throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
    117         }
    118 
    119         if (where >= mGapStart)
    120             return mText[where + mGapLength];
    121         else
    122             return mText[where];
    123     }
    124 
    125     /**
    126      * Return the number of chars in the buffer.
    127      */
    128     public int length() {
    129         return mText.length - mGapLength;
    130     }
    131 
    132     private void resizeFor(int size) {
    133         final int oldLength = mText.length;
    134         if (size + 1 <= oldLength) {
    135             return;
    136         }
    137 
    138         char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size));
    139         System.arraycopy(mText, 0, newText, 0, mGapStart);
    140         final int newLength = newText.length;
    141         final int delta = newLength - oldLength;
    142         final int after = oldLength - (mGapStart + mGapLength);
    143         System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
    144         mText = newText;
    145 
    146         mGapLength += delta;
    147         if (mGapLength < 1)
    148             new Exception("mGapLength < 1").printStackTrace();
    149 
    150         for (int i = 0; i < mSpanCount; i++) {
    151             if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
    152             if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
    153         }
    154     }
    155 
    156     private void moveGapTo(int where) {
    157         if (where == mGapStart)
    158             return;
    159 
    160         boolean atEnd = (where == length());
    161 
    162         if (where < mGapStart) {
    163             int overlap = mGapStart - where;
    164             System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
    165         } else /* where > mGapStart */ {
    166             int overlap = where - mGapStart;
    167             System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
    168         }
    169 
    170         // XXX be more clever
    171         for (int i = 0; i < mSpanCount; i++) {
    172             int start = mSpanStarts[i];
    173             int end = mSpanEnds[i];
    174 
    175             if (start > mGapStart)
    176                 start -= mGapLength;
    177             if (start > where)
    178                 start += mGapLength;
    179             else if (start == where) {
    180                 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
    181 
    182                 if (flag == POINT || (atEnd && flag == PARAGRAPH))
    183                     start += mGapLength;
    184             }
    185 
    186             if (end > mGapStart)
    187                 end -= mGapLength;
    188             if (end > where)
    189                 end += mGapLength;
    190             else if (end == where) {
    191                 int flag = (mSpanFlags[i] & END_MASK);
    192 
    193                 if (flag == POINT || (atEnd && flag == PARAGRAPH))
    194                     end += mGapLength;
    195             }
    196 
    197             mSpanStarts[i] = start;
    198             mSpanEnds[i] = end;
    199         }
    200 
    201         mGapStart = where;
    202     }
    203 
    204     // Documentation from interface
    205     public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
    206         return replace(where, where, tb, start, end);
    207     }
    208 
    209     // Documentation from interface
    210     public SpannableStringBuilder insert(int where, CharSequence tb) {
    211         return replace(where, where, tb, 0, tb.length());
    212     }
    213 
    214     // Documentation from interface
    215     public SpannableStringBuilder delete(int start, int end) {
    216         SpannableStringBuilder ret = replace(start, end, "", 0, 0);
    217 
    218         if (mGapLength > 2 * length())
    219             resizeFor(length());
    220 
    221         return ret; // == this
    222     }
    223 
    224     // Documentation from interface
    225     public void clear() {
    226         replace(0, length(), "", 0, 0);
    227     }
    228 
    229     // Documentation from interface
    230     public void clearSpans() {
    231         for (int i = mSpanCount - 1; i >= 0; i--) {
    232             Object what = mSpans[i];
    233             int ostart = mSpanStarts[i];
    234             int oend = mSpanEnds[i];
    235 
    236             if (ostart > mGapStart)
    237                 ostart -= mGapLength;
    238             if (oend > mGapStart)
    239                 oend -= mGapLength;
    240 
    241             mSpanCount = i;
    242             mSpans[i] = null;
    243 
    244             sendSpanRemoved(what, ostart, oend);
    245         }
    246     }
    247 
    248     // Documentation from interface
    249     public SpannableStringBuilder append(CharSequence text) {
    250         int length = length();
    251         return replace(length, length, text, 0, text.length());
    252     }
    253 
    254     /**
    255      * Appends the character sequence {@code text} and spans {@code what} over the appended part.
    256      * See {@link Spanned} for an explanation of what the flags mean.
    257      * @param text the character sequence to append.
    258      * @param what the object to be spanned over the appended text.
    259      * @param flags see {@link Spanned}.
    260      * @return this {@code SpannableStringBuilder}.
    261      */
    262     public SpannableStringBuilder append(CharSequence text, Object what, int flags) {
    263         int start = length();
    264         append(text);
    265         setSpan(what, start, length(), flags);
    266         return this;
    267     }
    268 
    269     // Documentation from interface
    270     public SpannableStringBuilder append(CharSequence text, int start, int end) {
    271         int length = length();
    272         return replace(length, length, text, start, end);
    273     }
    274 
    275     // Documentation from interface
    276     public SpannableStringBuilder append(char text) {
    277         return append(String.valueOf(text));
    278     }
    279 
    280     private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
    281         // Can be negative
    282         final int replacedLength = end - start;
    283         final int replacementLength = csEnd - csStart;
    284         final int nbNewChars = replacementLength - replacedLength;
    285 
    286         for (int i = mSpanCount - 1; i >= 0; i--) {
    287             int spanStart = mSpanStarts[i];
    288             if (spanStart > mGapStart)
    289                 spanStart -= mGapLength;
    290 
    291             int spanEnd = mSpanEnds[i];
    292             if (spanEnd > mGapStart)
    293                 spanEnd -= mGapLength;
    294 
    295             if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
    296                 int ost = spanStart;
    297                 int oen = spanEnd;
    298                 int clen = length();
    299 
    300                 if (spanStart > start && spanStart <= end) {
    301                     for (spanStart = end; spanStart < clen; spanStart++)
    302                         if (spanStart > end && charAt(spanStart - 1) == '\n')
    303                             break;
    304                 }
    305 
    306                 if (spanEnd > start && spanEnd <= end) {
    307                     for (spanEnd = end; spanEnd < clen; spanEnd++)
    308                         if (spanEnd > end && charAt(spanEnd - 1) == '\n')
    309                             break;
    310                 }
    311 
    312                 if (spanStart != ost || spanEnd != oen)
    313                     setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
    314             }
    315 
    316             int flags = 0;
    317             if (spanStart == start) flags |= SPAN_START_AT_START;
    318             else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
    319             if (spanEnd == start) flags |= SPAN_END_AT_START;
    320             else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
    321             mSpanFlags[i] |= flags;
    322         }
    323 
    324         moveGapTo(end);
    325 
    326         if (nbNewChars >= mGapLength) {
    327             resizeFor(mText.length + nbNewChars - mGapLength);
    328         }
    329 
    330         final boolean textIsRemoved = replacementLength == 0;
    331         // The removal pass needs to be done before the gap is updated in order to broadcast the
    332         // correct previous positions to the correct intersecting SpanWatchers
    333         if (replacedLength > 0) { // no need for span fixup on pure insertion
    334             // A for loop will not work because the array is being modified
    335             // Do not iterate in reverse to keep the SpanWatchers notified in ordering
    336             // Also, a removed SpanWatcher should not get notified of removed spans located
    337             // further in the span array.
    338             int i = 0;
    339             while (i < mSpanCount) {
    340                 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
    341                         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
    342                         mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
    343                         mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
    344                         // This condition indicates that the span would become empty
    345                         (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
    346                     removeSpan(i);
    347                     continue; // do not increment i, spans will be shifted left in the array
    348                 }
    349 
    350                 i++;
    351             }
    352         }
    353 
    354         mGapStart += nbNewChars;
    355         mGapLength -= nbNewChars;
    356 
    357         if (mGapLength < 1)
    358             new Exception("mGapLength < 1").printStackTrace();
    359 
    360         TextUtils.getChars(cs, csStart, csEnd, mText, start);
    361 
    362         if (replacedLength > 0) { // no need for span fixup on pure insertion
    363             final boolean atEnd = (mGapStart + mGapLength == mText.length);
    364 
    365             for (int i = 0; i < mSpanCount; i++) {
    366                 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
    367                 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
    368                         atEnd, textIsRemoved);
    369 
    370                 final int endFlag = (mSpanFlags[i] & END_MASK);
    371                 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
    372                         atEnd, textIsRemoved);
    373             }
    374         }
    375 
    376         mSpanCountBeforeAdd = mSpanCount;
    377 
    378         if (cs instanceof Spanned) {
    379             Spanned sp = (Spanned) cs;
    380             Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
    381 
    382             for (int i = 0; i < spans.length; i++) {
    383                 int st = sp.getSpanStart(spans[i]);
    384                 int en = sp.getSpanEnd(spans[i]);
    385 
    386                 if (st < csStart) st = csStart;
    387                 if (en > csEnd) en = csEnd;
    388 
    389                 // Add span only if this object is not yet used as a span in this string
    390                 if (getSpanStart(spans[i]) < 0) {
    391                     setSpan(false, spans[i], st - csStart + start, en - csStart + start,
    392                             sp.getSpanFlags(spans[i]));
    393                 }
    394             }
    395         }
    396     }
    397 
    398     private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd,
    399             boolean textIsRemoved) {
    400         if (offset >= start && offset < mGapStart + mGapLength) {
    401             if (flag == POINT) {
    402                 // A POINT located inside the replaced range should be moved to the end of the
    403                 // replaced text.
    404                 // The exception is when the point is at the start of the range and we are doing a
    405                 // text replacement (as opposed to a deletion): the point stays there.
    406                 if (textIsRemoved || offset > start) {
    407                     return mGapStart + mGapLength;
    408                 }
    409             } else {
    410                 if (flag == PARAGRAPH) {
    411                     if (atEnd) {
    412                         return mGapStart + mGapLength;
    413                     }
    414                 } else { // MARK
    415                     // MARKs should be moved to the start, with the exception of a mark located at
    416                     // the end of the range (which will be < mGapStart + mGapLength since mGapLength
    417                     // is > 0, which should stay 'unchanged' at the end of the replaced text.
    418                     if (textIsRemoved || offset < mGapStart - nbNewChars) {
    419                         return start;
    420                     } else {
    421                         // Move to the end of replaced text (needed if nbNewChars != 0)
    422                         return mGapStart;
    423                     }
    424                 }
    425             }
    426         }
    427         return offset;
    428     }
    429 
    430     private void removeSpan(int i) {
    431         Object object = mSpans[i];
    432 
    433         int start = mSpanStarts[i];
    434         int end = mSpanEnds[i];
    435 
    436         if (start > mGapStart) start -= mGapLength;
    437         if (end > mGapStart) end -= mGapLength;
    438 
    439         int count = mSpanCount - (i + 1);
    440         System.arraycopy(mSpans, i + 1, mSpans, i, count);
    441         System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
    442         System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
    443         System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
    444 
    445         mSpanCount--;
    446 
    447         mSpans[mSpanCount] = null;
    448 
    449         sendSpanRemoved(object, start, end);
    450     }
    451 
    452     // Documentation from interface
    453     public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
    454         return replace(start, end, tb, 0, tb.length());
    455     }
    456 
    457     // Documentation from interface
    458     public SpannableStringBuilder replace(final int start, final int end,
    459             CharSequence tb, int tbstart, int tbend) {
    460         checkRange("replace", start, end);
    461 
    462         int filtercount = mFilters.length;
    463         for (int i = 0; i < filtercount; i++) {
    464             CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
    465 
    466             if (repl != null) {
    467                 tb = repl;
    468                 tbstart = 0;
    469                 tbend = repl.length();
    470             }
    471         }
    472 
    473         final int origLen = end - start;
    474         final int newLen = tbend - tbstart;
    475 
    476         if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
    477             // This is a no-op iif there are no spans in tb that would be added (with a 0-length)
    478             // Early exit so that the text watchers do not get notified
    479             return this;
    480         }
    481 
    482         TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
    483         sendBeforeTextChanged(textWatchers, start, origLen, newLen);
    484 
    485         // Try to keep the cursor / selection at the same relative position during
    486         // a text replacement. If replaced or replacement text length is zero, this
    487         // is already taken care of.
    488         boolean adjustSelection = origLen != 0 && newLen != 0;
    489         int selectionStart = 0;
    490         int selectionEnd = 0;
    491         if (adjustSelection) {
    492             selectionStart = Selection.getSelectionStart(this);
    493             selectionEnd = Selection.getSelectionEnd(this);
    494         }
    495 
    496         change(start, end, tb, tbstart, tbend);
    497 
    498         if (adjustSelection) {
    499             if (selectionStart > start && selectionStart < end) {
    500                 final int offset = (selectionStart - start) * newLen / origLen;
    501                 selectionStart = start + offset;
    502 
    503                 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
    504                         Spanned.SPAN_POINT_POINT);
    505             }
    506             if (selectionEnd > start && selectionEnd < end) {
    507                 final int offset = (selectionEnd - start) * newLen / origLen;
    508                 selectionEnd = start + offset;
    509 
    510                 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
    511                         Spanned.SPAN_POINT_POINT);
    512             }
    513         }
    514 
    515         sendTextChanged(textWatchers, start, origLen, newLen);
    516         sendAfterTextChanged(textWatchers);
    517 
    518         // Span watchers need to be called after text watchers, which may update the layout
    519         sendToSpanWatchers(start, end, newLen - origLen);
    520 
    521         return this;
    522     }
    523 
    524     private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
    525         if (text instanceof Spanned) {
    526             Spanned spanned = (Spanned) text;
    527             Object[] spans = spanned.getSpans(offset, offset, Object.class);
    528             final int length = spans.length;
    529             for (int i = 0; i < length; i++) {
    530                 Object span = spans[i];
    531                 int flags = spanned.getSpanFlags(span);
    532                 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
    533             }
    534         }
    535         return false;
    536     }
    537 
    538     private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
    539         for (int i = 0; i < mSpanCountBeforeAdd; i++) {
    540             int spanStart = mSpanStarts[i];
    541             int spanEnd = mSpanEnds[i];
    542             if (spanStart > mGapStart) spanStart -= mGapLength;
    543             if (spanEnd > mGapStart) spanEnd -= mGapLength;
    544             int spanFlags = mSpanFlags[i];
    545 
    546             int newReplaceEnd = replaceEnd + nbNewChars;
    547             boolean spanChanged = false;
    548 
    549             int previousSpanStart = spanStart;
    550             if (spanStart > newReplaceEnd) {
    551                 if (nbNewChars != 0) {
    552                     previousSpanStart -= nbNewChars;
    553                     spanChanged = true;
    554                 }
    555             } else if (spanStart >= replaceStart) {
    556                 // No change if span start was already at replace interval boundaries before replace
    557                 if ((spanStart != replaceStart ||
    558                         ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
    559                         (spanStart != newReplaceEnd ||
    560                         ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
    561                     // TODO A correct previousSpanStart cannot be computed at this point.
    562                     // It would require to save all the previous spans' positions before the replace
    563                     // Using an invalid -1 value to convey this would break the broacast range
    564                     spanChanged = true;
    565                 }
    566             }
    567 
    568             int previousSpanEnd = spanEnd;
    569             if (spanEnd > newReplaceEnd) {
    570                 if (nbNewChars != 0) {
    571                     previousSpanEnd -= nbNewChars;
    572                     spanChanged = true;
    573                 }
    574             } else if (spanEnd >= replaceStart) {
    575                 // No change if span start was already at replace interval boundaries before replace
    576                 if ((spanEnd != replaceStart ||
    577                         ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
    578                         (spanEnd != newReplaceEnd ||
    579                         ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
    580                     // TODO same as above for previousSpanEnd
    581                     spanChanged = true;
    582                 }
    583             }
    584 
    585             if (spanChanged) {
    586                 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
    587             }
    588             mSpanFlags[i] &= ~SPAN_START_END_MASK;
    589         }
    590 
    591         // The spans starting at mIntermediateSpanCount were added from the replacement text
    592         for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
    593             int spanStart = mSpanStarts[i];
    594             int spanEnd = mSpanEnds[i];
    595             if (spanStart > mGapStart) spanStart -= mGapLength;
    596             if (spanEnd > mGapStart) spanEnd -= mGapLength;
    597             sendSpanAdded(mSpans[i], spanStart, spanEnd);
    598         }
    599     }
    600 
    601     /**
    602      * Mark the specified range of text with the specified object.
    603      * The flags determine how the span will behave when text is
    604      * inserted at the start or end of the span's range.
    605      */
    606     public void setSpan(Object what, int start, int end, int flags) {
    607         setSpan(true, what, start, end, flags);
    608     }
    609 
    610     private void setSpan(boolean send, Object what, int start, int end, int flags) {
    611         checkRange("setSpan", start, end);
    612 
    613         int flagsStart = (flags & START_MASK) >> START_SHIFT;
    614         if (flagsStart == PARAGRAPH) {
    615             if (start != 0 && start != length()) {
    616                 char c = charAt(start - 1);
    617 
    618                 if (c != '\n')
    619                     throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
    620             }
    621         }
    622 
    623         int flagsEnd = flags & END_MASK;
    624         if (flagsEnd == PARAGRAPH) {
    625             if (end != 0 && end != length()) {
    626                 char c = charAt(end - 1);
    627 
    628                 if (c != '\n')
    629                     throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
    630             }
    631         }
    632 
    633         // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
    634         if (flagsStart == POINT && flagsEnd == MARK && start == end) {
    635             if (send) {
    636                 Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
    637             }
    638             // Silently ignore invalid spans when they are created from this class.
    639             // This avoids the duplication of the above test code before all the
    640             // calls to setSpan that are done in this class
    641             return;
    642         }
    643 
    644         int nstart = start;
    645         int nend = end;
    646 
    647         if (start > mGapStart) {
    648             start += mGapLength;
    649         } else if (start == mGapStart) {
    650             if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
    651                 start += mGapLength;
    652         }
    653 
    654         if (end > mGapStart) {
    655             end += mGapLength;
    656         } else if (end == mGapStart) {
    657             if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
    658                 end += mGapLength;
    659         }
    660 
    661         int count = mSpanCount;
    662         Object[] spans = mSpans;
    663 
    664         for (int i = 0; i < count; i++) {
    665             if (spans[i] == what) {
    666                 int ostart = mSpanStarts[i];
    667                 int oend = mSpanEnds[i];
    668 
    669                 if (ostart > mGapStart)
    670                     ostart -= mGapLength;
    671                 if (oend > mGapStart)
    672                     oend -= mGapLength;
    673 
    674                 mSpanStarts[i] = start;
    675                 mSpanEnds[i] = end;
    676                 mSpanFlags[i] = flags;
    677 
    678                 if (send) sendSpanChanged(what, ostart, oend, nstart, nend);
    679 
    680                 return;
    681             }
    682         }
    683 
    684         mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
    685         mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
    686         mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
    687         mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
    688         mSpanCount++;
    689 
    690         if (send) sendSpanAdded(what, nstart, nend);
    691     }
    692 
    693     /**
    694      * Remove the specified markup object from the buffer.
    695      */
    696     public void removeSpan(Object what) {
    697         for (int i = mSpanCount - 1; i >= 0; i--) {
    698             if (mSpans[i] == what) {
    699                 removeSpan(i);
    700                 return;
    701             }
    702         }
    703     }
    704 
    705     /**
    706      * Return the buffer offset of the beginning of the specified
    707      * markup object, or -1 if it is not attached to this buffer.
    708      */
    709     public int getSpanStart(Object what) {
    710         int count = mSpanCount;
    711         Object[] spans = mSpans;
    712 
    713         for (int i = count - 1; i >= 0; i--) {
    714             if (spans[i] == what) {
    715                 int where = mSpanStarts[i];
    716 
    717                 if (where > mGapStart)
    718                     where -= mGapLength;
    719 
    720                 return where;
    721             }
    722         }
    723 
    724         return -1;
    725     }
    726 
    727     /**
    728      * Return the buffer offset of the end of the specified
    729      * markup object, or -1 if it is not attached to this buffer.
    730      */
    731     public int getSpanEnd(Object what) {
    732         int count = mSpanCount;
    733         Object[] spans = mSpans;
    734 
    735         for (int i = count - 1; i >= 0; i--) {
    736             if (spans[i] == what) {
    737                 int where = mSpanEnds[i];
    738 
    739                 if (where > mGapStart)
    740                     where -= mGapLength;
    741 
    742                 return where;
    743             }
    744         }
    745 
    746         return -1;
    747     }
    748 
    749     /**
    750      * Return the flags of the end of the specified
    751      * markup object, or 0 if it is not attached to this buffer.
    752      */
    753     public int getSpanFlags(Object what) {
    754         int count = mSpanCount;
    755         Object[] spans = mSpans;
    756 
    757         for (int i = count - 1; i >= 0; i--) {
    758             if (spans[i] == what) {
    759                 return mSpanFlags[i];
    760             }
    761         }
    762 
    763         return 0;
    764     }
    765 
    766     /**
    767      * Return an array of the spans of the specified type that overlap
    768      * the specified range of the buffer.  The kind may be Object.class to get
    769      * a list of all the spans regardless of type.
    770      */
    771     @SuppressWarnings("unchecked")
    772     public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
    773         if (kind == null) return ArrayUtils.emptyArray(kind);
    774 
    775         int spanCount = mSpanCount;
    776         Object[] spans = mSpans;
    777         int[] starts = mSpanStarts;
    778         int[] ends = mSpanEnds;
    779         int[] flags = mSpanFlags;
    780         int gapstart = mGapStart;
    781         int gaplen = mGapLength;
    782 
    783         int count = 0;
    784         T[] ret = null;
    785         T ret1 = null;
    786 
    787         for (int i = 0; i < spanCount; i++) {
    788             int spanStart = starts[i];
    789             if (spanStart > gapstart) {
    790                 spanStart -= gaplen;
    791             }
    792             if (spanStart > queryEnd) {
    793                 continue;
    794             }
    795 
    796             int spanEnd = ends[i];
    797             if (spanEnd > gapstart) {
    798                 spanEnd -= gaplen;
    799             }
    800             if (spanEnd < queryStart) {
    801                 continue;
    802             }
    803 
    804             if (spanStart != spanEnd && queryStart != queryEnd) {
    805                 if (spanStart == queryEnd)
    806                     continue;
    807                 if (spanEnd == queryStart)
    808                     continue;
    809             }
    810 
    811             // Expensive test, should be performed after the previous tests
    812             if (!kind.isInstance(spans[i])) continue;
    813 
    814             if (count == 0) {
    815                 // Safe conversion thanks to the isInstance test above
    816                 ret1 = (T) spans[i];
    817                 count++;
    818             } else {
    819                 if (count == 1) {
    820                     // Safe conversion, but requires a suppressWarning
    821                     ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
    822                     ret[0] = ret1;
    823                 }
    824 
    825                 int prio = flags[i] & SPAN_PRIORITY;
    826                 if (prio != 0) {
    827                     int j;
    828 
    829                     for (j = 0; j < count; j++) {
    830                         int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
    831 
    832                         if (prio > p) {
    833                             break;
    834                         }
    835                     }
    836 
    837                     System.arraycopy(ret, j, ret, j + 1, count - j);
    838                     // Safe conversion thanks to the isInstance test above
    839                     ret[j] = (T) spans[i];
    840                     count++;
    841                 } else {
    842                     // Safe conversion thanks to the isInstance test above
    843                     ret[count++] = (T) spans[i];
    844                 }
    845             }
    846         }
    847 
    848         if (count == 0) {
    849             return ArrayUtils.emptyArray(kind);
    850         }
    851         if (count == 1) {
    852             // Safe conversion, but requires a suppressWarning
    853             ret = (T[]) Array.newInstance(kind, 1);
    854             ret[0] = ret1;
    855             return ret;
    856         }
    857         if (count == ret.length) {
    858             return ret;
    859         }
    860 
    861         // Safe conversion, but requires a suppressWarning
    862         T[] nret = (T[]) Array.newInstance(kind, count);
    863         System.arraycopy(ret, 0, nret, 0, count);
    864         return nret;
    865     }
    866 
    867     /**
    868      * Return the next offset after <code>start</code> but less than or
    869      * equal to <code>limit</code> where a span of the specified type
    870      * begins or ends.
    871      */
    872     public int nextSpanTransition(int start, int limit, Class kind) {
    873         int count = mSpanCount;
    874         Object[] spans = mSpans;
    875         int[] starts = mSpanStarts;
    876         int[] ends = mSpanEnds;
    877         int gapstart = mGapStart;
    878         int gaplen = mGapLength;
    879 
    880         if (kind == null) {
    881             kind = Object.class;
    882         }
    883 
    884         for (int i = 0; i < count; i++) {
    885             int st = starts[i];
    886             int en = ends[i];
    887 
    888             if (st > gapstart)
    889                 st -= gaplen;
    890             if (en > gapstart)
    891                 en -= gaplen;
    892 
    893             if (st > start && st < limit && kind.isInstance(spans[i]))
    894                 limit = st;
    895             if (en > start && en < limit && kind.isInstance(spans[i]))
    896                 limit = en;
    897         }
    898 
    899         return limit;
    900     }
    901 
    902     /**
    903      * Return a new CharSequence containing a copy of the specified
    904      * range of this buffer, including the overlapping spans.
    905      */
    906     public CharSequence subSequence(int start, int end) {
    907         return new SpannableStringBuilder(this, start, end);
    908     }
    909 
    910     /**
    911      * Copy the specified range of chars from this buffer into the
    912      * specified array, beginning at the specified offset.
    913      */
    914     public void getChars(int start, int end, char[] dest, int destoff) {
    915         checkRange("getChars", start, end);
    916 
    917         if (end <= mGapStart) {
    918             System.arraycopy(mText, start, dest, destoff, end - start);
    919         } else if (start >= mGapStart) {
    920             System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
    921         } else {
    922             System.arraycopy(mText, start, dest, destoff, mGapStart - start);
    923             System.arraycopy(mText, mGapStart + mGapLength,
    924                     dest, destoff + (mGapStart - start),
    925                     end - mGapStart);
    926         }
    927     }
    928 
    929     /**
    930      * Return a String containing a copy of the chars in this buffer.
    931      */
    932     @Override
    933     public String toString() {
    934         int len = length();
    935         char[] buf = new char[len];
    936 
    937         getChars(0, len, buf, 0);
    938         return new String(buf);
    939     }
    940 
    941     /**
    942      * Return a String containing a copy of the chars in this buffer, limited to the
    943      * [start, end[ range.
    944      * @hide
    945      */
    946     public String substring(int start, int end) {
    947         char[] buf = new char[end - start];
    948         getChars(start, end, buf, 0);
    949         return new String(buf);
    950     }
    951 
    952     private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
    953         int n = watchers.length;
    954 
    955         for (int i = 0; i < n; i++) {
    956             watchers[i].beforeTextChanged(this, start, before, after);
    957         }
    958     }
    959 
    960     private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
    961         int n = watchers.length;
    962 
    963         for (int i = 0; i < n; i++) {
    964             watchers[i].onTextChanged(this, start, before, after);
    965         }
    966     }
    967 
    968     private void sendAfterTextChanged(TextWatcher[] watchers) {
    969         int n = watchers.length;
    970 
    971         for (int i = 0; i < n; i++) {
    972             watchers[i].afterTextChanged(this);
    973         }
    974     }
    975 
    976     private void sendSpanAdded(Object what, int start, int end) {
    977         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
    978         int n = recip.length;
    979 
    980         for (int i = 0; i < n; i++) {
    981             recip[i].onSpanAdded(this, what, start, end);
    982         }
    983     }
    984 
    985     private void sendSpanRemoved(Object what, int start, int end) {
    986         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
    987         int n = recip.length;
    988 
    989         for (int i = 0; i < n; i++) {
    990             recip[i].onSpanRemoved(this, what, start, end);
    991         }
    992     }
    993 
    994     private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
    995         // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
    996         // called, so that the order of the span does not affect this broadcast.
    997         SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
    998                 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
    999         int n = spanWatchers.length;
   1000         for (int i = 0; i < n; i++) {
   1001             spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
   1002         }
   1003     }
   1004 
   1005     private static String region(int start, int end) {
   1006         return "(" + start + " ... " + end + ")";
   1007     }
   1008 
   1009     private void checkRange(final String operation, int start, int end) {
   1010         if (end < start) {
   1011             throw new IndexOutOfBoundsException(operation + " " +
   1012                     region(start, end) + " has end before start");
   1013         }
   1014 
   1015         int len = length();
   1016 
   1017         if (start > len || end > len) {
   1018             throw new IndexOutOfBoundsException(operation + " " +
   1019                     region(start, end) + " ends beyond length " + len);
   1020         }
   1021 
   1022         if (start < 0 || end < 0) {
   1023             throw new IndexOutOfBoundsException(operation + " " +
   1024                     region(start, end) + " starts before 0");
   1025         }
   1026     }
   1027 
   1028     /*
   1029     private boolean isprint(char c) { // XXX
   1030         if (c >= ' ' && c <= '~')
   1031             return true;
   1032         else
   1033             return false;
   1034     }
   1035 
   1036     private static final int startFlag(int flag) {
   1037         return (flag >> 4) & 0x0F;
   1038     }
   1039 
   1040     private static final int endFlag(int flag) {
   1041         return flag & 0x0F;
   1042     }
   1043 
   1044     public void dump() { // XXX
   1045         for (int i = 0; i < mGapStart; i++) {
   1046             System.out.print('|');
   1047             System.out.print(' ');
   1048             System.out.print(isprint(mText[i]) ? mText[i] : '.');
   1049             System.out.print(' ');
   1050         }
   1051 
   1052         for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
   1053             System.out.print('|');
   1054             System.out.print('(');
   1055             System.out.print(isprint(mText[i]) ? mText[i] : '.');
   1056             System.out.print(')');
   1057         }
   1058 
   1059         for (int i = mGapStart + mGapLength; i < mText.length; i++) {
   1060             System.out.print('|');
   1061             System.out.print(' ');
   1062             System.out.print(isprint(mText[i]) ? mText[i] : '.');
   1063             System.out.print(' ');
   1064         }
   1065 
   1066         System.out.print('\n');
   1067 
   1068         for (int i = 0; i < mText.length + 1; i++) {
   1069             int found = 0;
   1070             int wfound = 0;
   1071 
   1072             for (int j = 0; j < mSpanCount; j++) {
   1073                 if (mSpanStarts[j] == i) {
   1074                     found = 1;
   1075                     wfound = j;
   1076                     break;
   1077                 }
   1078 
   1079                 if (mSpanEnds[j] == i) {
   1080                     found = 2;
   1081                     wfound = j;
   1082                     break;
   1083                 }
   1084             }
   1085 
   1086             if (found == 1) {
   1087                 if (startFlag(mSpanFlags[wfound]) == MARK)
   1088                     System.out.print("(   ");
   1089                 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
   1090                     System.out.print("<   ");
   1091                 else
   1092                     System.out.print("[   ");
   1093             } else if (found == 2) {
   1094                 if (endFlag(mSpanFlags[wfound]) == POINT)
   1095                     System.out.print(")   ");
   1096                 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
   1097                     System.out.print(">   ");
   1098                 else
   1099                     System.out.print("]   ");
   1100             } else {
   1101                 System.out.print("    ");
   1102             }
   1103         }
   1104 
   1105         System.out.print("\n");
   1106     }
   1107     */
   1108 
   1109     /**
   1110      * Don't call this yourself -- exists for Canvas to use internally.
   1111      * {@hide}
   1112      */
   1113     public void drawText(Canvas c, int start, int end, float x, float y, Paint p) {
   1114         checkRange("drawText", start, end);
   1115 
   1116         if (end <= mGapStart) {
   1117             c.drawText(mText, start, end - start, x, y, p);
   1118         } else if (start >= mGapStart) {
   1119             c.drawText(mText, start + mGapLength, end - start, x, y, p);
   1120         } else {
   1121             char[] buf = TextUtils.obtain(end - start);
   1122 
   1123             getChars(start, end, buf, 0);
   1124             c.drawText(buf, 0, end - start, x, y, p);
   1125             TextUtils.recycle(buf);
   1126         }
   1127     }
   1128 
   1129 
   1130     /**
   1131      * Don't call this yourself -- exists for Canvas to use internally.
   1132      * {@hide}
   1133      */
   1134     public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
   1135             float x, float y, boolean isRtl, Paint p) {
   1136         checkRange("drawTextRun", start, end);
   1137 
   1138         int contextLen = contextEnd - contextStart;
   1139         int len = end - start;
   1140         if (contextEnd <= mGapStart) {
   1141             c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p);
   1142         } else if (contextStart >= mGapStart) {
   1143             c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
   1144                     contextLen, x, y, isRtl, p);
   1145         } else {
   1146             char[] buf = TextUtils.obtain(contextLen);
   1147             getChars(contextStart, contextEnd, buf, 0);
   1148             c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p);
   1149             TextUtils.recycle(buf);
   1150         }
   1151     }
   1152 
   1153     /**
   1154      * Don't call this yourself -- exists for Paint to use internally.
   1155      * {@hide}
   1156      */
   1157     public float measureText(int start, int end, Paint p) {
   1158         checkRange("measureText", start, end);
   1159 
   1160         float ret;
   1161 
   1162         if (end <= mGapStart) {
   1163             ret = p.measureText(mText, start, end - start);
   1164         } else if (start >= mGapStart) {
   1165             ret = p.measureText(mText, start + mGapLength, end - start);
   1166         } else {
   1167             char[] buf = TextUtils.obtain(end - start);
   1168 
   1169             getChars(start, end, buf, 0);
   1170             ret = p.measureText(buf, 0, end - start);
   1171             TextUtils.recycle(buf);
   1172         }
   1173 
   1174         return ret;
   1175     }
   1176 
   1177     /**
   1178      * Don't call this yourself -- exists for Paint to use internally.
   1179      * {@hide}
   1180      */
   1181     public int getTextWidths(int start, int end, float[] widths, Paint p) {
   1182         checkRange("getTextWidths", start, end);
   1183 
   1184         int ret;
   1185 
   1186         if (end <= mGapStart) {
   1187             ret = p.getTextWidths(mText, start, end - start, widths);
   1188         } else if (start >= mGapStart) {
   1189             ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
   1190         } else {
   1191             char[] buf = TextUtils.obtain(end - start);
   1192 
   1193             getChars(start, end, buf, 0);
   1194             ret = p.getTextWidths(buf, 0, end - start, widths);
   1195             TextUtils.recycle(buf);
   1196         }
   1197 
   1198         return ret;
   1199     }
   1200 
   1201     /**
   1202      * Don't call this yourself -- exists for Paint to use internally.
   1203      * {@hide}
   1204      */
   1205     public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl,
   1206             float[] advances, int advancesPos, Paint p) {
   1207 
   1208         float ret;
   1209 
   1210         int contextLen = contextEnd - contextStart;
   1211         int len = end - start;
   1212 
   1213         if (end <= mGapStart) {
   1214             ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
   1215                     isRtl, advances, advancesPos);
   1216         } else if (start >= mGapStart) {
   1217             ret = p.getTextRunAdvances(mText, start + mGapLength, len,
   1218                     contextStart + mGapLength, contextLen, isRtl, advances, advancesPos);
   1219         } else {
   1220             char[] buf = TextUtils.obtain(contextLen);
   1221             getChars(contextStart, contextEnd, buf, 0);
   1222             ret = p.getTextRunAdvances(buf, start - contextStart, len,
   1223                     0, contextLen, isRtl, advances, advancesPos);
   1224             TextUtils.recycle(buf);
   1225         }
   1226 
   1227         return ret;
   1228     }
   1229 
   1230     /**
   1231      * Returns the next cursor position in the run.  This avoids placing the cursor between
   1232      * surrogates, between characters that form conjuncts, between base characters and combining
   1233      * marks, or within a reordering cluster.
   1234      *
   1235      * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
   1236      * span enclosing the cursor in the direction of movement.
   1237      * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
   1238      * the start of the string.</p>
   1239      *
   1240      * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
   1241      * this returns -1.  Otherwise this will never return a value before contextStart or after
   1242      * contextEnd.</p>
   1243      *
   1244      * @param contextStart the start index of the context
   1245      * @param contextEnd the (non-inclusive) end index of the context
   1246      * @param dir either DIRECTION_RTL or DIRECTION_LTR
   1247      * @param offset the cursor position to move from
   1248      * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
   1249      * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
   1250      * CURSOR_AT_OR_BEFORE, or CURSOR_AT
   1251      * @param p the Paint object that is requesting this information
   1252      * @return the offset of the next position, or -1
   1253      * @deprecated This is an internal method, refrain from using it in your code
   1254      */
   1255     @Deprecated
   1256     public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset,
   1257             int cursorOpt, Paint p) {
   1258 
   1259         int ret;
   1260 
   1261         int contextLen = contextEnd - contextStart;
   1262         if (contextEnd <= mGapStart) {
   1263             ret = p.getTextRunCursor(mText, contextStart, contextLen,
   1264                     dir, offset, cursorOpt);
   1265         } else if (contextStart >= mGapStart) {
   1266             ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
   1267                     dir, offset + mGapLength, cursorOpt) - mGapLength;
   1268         } else {
   1269             char[] buf = TextUtils.obtain(contextLen);
   1270             getChars(contextStart, contextEnd, buf, 0);
   1271             ret = p.getTextRunCursor(buf, 0, contextLen,
   1272                     dir, offset - contextStart, cursorOpt) + contextStart;
   1273             TextUtils.recycle(buf);
   1274         }
   1275 
   1276         return ret;
   1277     }
   1278 
   1279     // Documentation from interface
   1280     public void setFilters(InputFilter[] filters) {
   1281         if (filters == null) {
   1282             throw new IllegalArgumentException();
   1283         }
   1284 
   1285         mFilters = filters;
   1286     }
   1287 
   1288     // Documentation from interface
   1289     public InputFilter[] getFilters() {
   1290         return mFilters;
   1291     }
   1292 
   1293     // Same as SpannableStringInternal
   1294     @Override
   1295     public boolean equals(Object o) {
   1296         if (o instanceof Spanned &&
   1297                 toString().equals(o.toString())) {
   1298             Spanned other = (Spanned) o;
   1299             // Check span data
   1300             Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
   1301             if (mSpanCount == otherSpans.length) {
   1302                 for (int i = 0; i < mSpanCount; ++i) {
   1303                     Object thisSpan = mSpans[i];
   1304                     Object otherSpan = otherSpans[i];
   1305                     if (thisSpan == this) {
   1306                         if (other != otherSpan ||
   1307                                 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
   1308                                 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
   1309                                 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
   1310                             return false;
   1311                         }
   1312                     } else if (!thisSpan.equals(otherSpan) ||
   1313                             getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
   1314                             getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
   1315                             getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
   1316                         return false;
   1317                     }
   1318                 }
   1319                 return true;
   1320             }
   1321         }
   1322         return false;
   1323     }
   1324 
   1325     // Same as SpannableStringInternal
   1326     @Override
   1327     public int hashCode() {
   1328         int hash = toString().hashCode();
   1329         hash = hash * 31 + mSpanCount;
   1330         for (int i = 0; i < mSpanCount; ++i) {
   1331             Object span = mSpans[i];
   1332             if (span != this) {
   1333                 hash = hash * 31 + span.hashCode();
   1334             }
   1335             hash = hash * 31 + getSpanStart(span);
   1336             hash = hash * 31 + getSpanEnd(span);
   1337             hash = hash * 31 + getSpanFlags(span);
   1338         }
   1339         return hash;
   1340     }
   1341 
   1342     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
   1343     private InputFilter[] mFilters = NO_FILTERS;
   1344 
   1345     private char[] mText;
   1346     private int mGapStart;
   1347     private int mGapLength;
   1348 
   1349     private Object[] mSpans;
   1350     private int[] mSpanStarts;
   1351     private int[] mSpanEnds;
   1352     private int[] mSpanFlags;
   1353     private int mSpanCount;
   1354     private int mSpanCountBeforeAdd;
   1355 
   1356     // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
   1357     private static final int MARK = 1;
   1358     private static final int POINT = 2;
   1359     private static final int PARAGRAPH = 3;
   1360 
   1361     private static final int START_MASK = 0xF0;
   1362     private static final int END_MASK = 0x0F;
   1363     private static final int START_SHIFT = 4;
   1364 
   1365     // These bits are not (currently) used by SPANNED flags
   1366     private static final int SPAN_START_AT_START = 0x1000;
   1367     private static final int SPAN_START_AT_END = 0x2000;
   1368     private static final int SPAN_END_AT_START = 0x4000;
   1369     private static final int SPAN_END_AT_END = 0x8000;
   1370     private static final int SPAN_START_END_MASK = 0xF000;
   1371 }
   1372