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