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