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