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.compat.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      */
SpannableStringBuilder()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      */
SpannableStringBuilder(CharSequence text)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      */
SpannableStringBuilder(CharSequence text, int start, int end)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 
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, 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 
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                             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 
updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd, boolean textIsRemoved)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.
removeSpan(int i, int flags)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
replace(int start, int end, CharSequence tb)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
replace(final int start, final int end, CharSequence tb, int tbstart, int tbend)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 
hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset)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
sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars)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      */
setSpan(Object what, int start, int end, int flags)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.
setSpan(boolean send, Object what, int start, int end, int flags, boolean enforceParagraph)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 
isInvalidParagraph(int index, int flag)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      */
removeSpan(Object what)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      */
removeSpan(Object what, int flags)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      */
resolveGap(int i)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      */
getSpanStart(Object what)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      */
getSpanEnd(Object what)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      */
getSpanFlags(Object what)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")
getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind)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
getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind, boolean sortByInsertionOrder)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 
countSpans(int queryStart, int queryEnd, Class kind, int i)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")
getSpansRec(int queryStart, int queryEnd, Class<T> kind, int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort)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      */
obtain(final int elementCount)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      */
recycle(int[] buffer)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      */
checkSortBuffer(int[] buffer, int size)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      */
sort(T[] array, int[] priority, int[] insertionOrder)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      */
siftDown(int index, T[] array, int size, int[] priority, int[] insertionOrder)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      */
compareSpans(int left, int right, int[] priority, int[] insertionOrder)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      */
nextSpanTransition(int start, int limit, Class kind)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 
nextSpanTransitionRec(int start, int limit, Class kind, int i)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      */
subSequence(int start, int end)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      */
getChars(int start, int end, char[] dest, int destoff)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
toString()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
substring(int start, int end)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      */
getTextWatcherDepth()1248     public int getTextWatcherDepth() {
1249         return mTextWatcherDepth;
1250     }
1251 
sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after)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 
sendTextChanged(TextWatcher[] watchers, int start, int before, int after)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 
sendAfterTextChanged(TextWatcher[] watchers)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 
sendSpanAdded(Object what, int start, int end)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 
sendSpanRemoved(Object what, int start, int end)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 
sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end)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 
region(int start, int end)1311     private static String region(int start, int end) {
1312         return "(" + start + " ... " + end + ")";
1313     }
1314 
checkRange(final String operation, int start, int end)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
drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)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
drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)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      */
measureText(int start, int end, Paint p)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      */
getTextWidths(int start, int end, float[] widths, Paint p)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      */
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesPos, Paint p)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
getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p)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
getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)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
setFilters(InputFilter[] filters)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
getFilters()1604     public InputFilter[] getFilters() {
1605         return mFilters;
1606     }
1607 
1608     // Same as SpannableStringInternal
1609     @Override
equals(Object o)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
hashCode()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 
treeRoot()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)
leftChild(int i)1688     private static int leftChild(int i) {
1689         return i - (((i + 1) & ~i) >> 1);
1690     }
1691 
rightChild(int i)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.
calcMax(int i)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
restoreInvariants()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
invalidateIndex(int i)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