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