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