blob: 0d29da00f7c972a380156f6aaead85e606545fa5 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.text;
18
Roozbeh Pournadercd217b82015-08-11 16:51:21 -070019import android.annotation.Nullable;
Mathew Inwoodefeab842018-08-14 15:21:30 +010020import android.annotation.UnsupportedAppUsage;
John Reckcaa08ff2016-10-07 13:21:36 -070021import android.graphics.BaseCanvas;
Doug Feltf47d7402010-04-21 16:01:52 -070022import android.graphics.Paint;
Gilles Debunne34ec2b52012-04-10 13:25:33 -070023import android.util.Log;
Gilles Debunne6435a562011-08-04 21:22:30 -070024
Siyamed Sinir1bcdcebf2016-12-14 13:24:47 -080025import com.android.internal.annotations.GuardedBy;
Gilles Debunne6435a562011-08-04 21:22:30 -070026import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050027import com.android.internal.util.GrowingArrayUtils;
28
29import libcore.util.EmptyArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030
31import java.lang.reflect.Array;
Raph Leviend7c020e2015-02-04 20:09:03 -080032import java.util.IdentityHashMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
34/**
35 * This is the class for text whose content and markup can both be changed.
36 */
Gilles Debunne6435a562011-08-04 21:22:30 -070037public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
38 Appendable, GraphicsOperations {
Jean Chalard84a33202014-02-13 18:24:36 +090039 private final static String TAG = "SpannableStringBuilder";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 /**
41 * Create a new SpannableStringBuilder with empty contents
42 */
43 public SpannableStringBuilder() {
44 this("");
45 }
46
47 /**
48 * Create a new SpannableStringBuilder containing a copy of the
49 * specified text, including its spans if any.
50 */
51 public SpannableStringBuilder(CharSequence text) {
52 this(text, 0, text.length());
53 }
54
55 /**
56 * Create a new SpannableStringBuilder containing a copy of the
57 * specified slice of the specified text, including its spans if any.
58 */
59 public SpannableStringBuilder(CharSequence text, int start, int end) {
60 int srclen = end - start;
61
Gilles Debunne0249b432012-04-09 16:02:31 -070062 if (srclen < 0) throw new StringIndexOutOfBoundsException();
63
Adam Lesinski776abc22014-03-07 11:30:59 -050064 mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 mGapStart = srclen;
Adam Lesinski776abc22014-03-07 11:30:59 -050066 mGapLength = mText.length - srclen;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067
68 TextUtils.getChars(text, start, end, mText, 0);
69
70 mSpanCount = 0;
Siyamed Sinirfa05ba02016-01-12 10:54:43 -080071 mSpanInsertCount = 0;
Adam Lesinski776abc22014-03-07 11:30:59 -050072 mSpans = EmptyArray.OBJECT;
73 mSpanStarts = EmptyArray.INT;
74 mSpanEnds = EmptyArray.INT;
75 mSpanFlags = EmptyArray.INT;
Raph Leviend7c020e2015-02-04 20:09:03 -080076 mSpanMax = EmptyArray.INT;
Siyamed Sinirfa05ba02016-01-12 10:54:43 -080077 mSpanOrder = EmptyArray.INT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078
79 if (text instanceof Spanned) {
80 Spanned sp = (Spanned) text;
81 Object[] spans = sp.getSpans(start, end, Object.class);
82
83 for (int i = 0; i < spans.length; i++) {
84 if (spans[i] instanceof NoCopySpan) {
85 continue;
86 }
Gilles Debunne174c44c2012-04-10 16:01:09 -070087
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 int st = sp.getSpanStart(spans[i]) - start;
89 int en = sp.getSpanEnd(spans[i]) - start;
90 int fl = sp.getSpanFlags(spans[i]);
91
92 if (st < 0)
93 st = 0;
94 if (st > end - start)
95 st = end - start;
96
97 if (en < 0)
98 en = 0;
99 if (en > end - start)
100 en = end - start;
101
Siyamed Sinira6284a22017-04-10 21:42:21 -0700102 setSpan(false, spans[i], st, en, fl, false/*enforceParagraph*/);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800104 restoreInvariants();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 }
106 }
107
108 public static SpannableStringBuilder valueOf(CharSequence source) {
109 if (source instanceof SpannableStringBuilder) {
110 return (SpannableStringBuilder) source;
111 } else {
112 return new SpannableStringBuilder(source);
113 }
114 }
115
116 /**
117 * Return the char at the specified offset within the buffer.
118 */
119 public char charAt(int where) {
120 int len = length();
121 if (where < 0) {
122 throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
123 } else if (where >= len) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700124 throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 }
126
127 if (where >= mGapStart)
128 return mText[where + mGapLength];
129 else
130 return mText[where];
131 }
132
133 /**
134 * Return the number of chars in the buffer.
135 */
136 public int length() {
137 return mText.length - mGapLength;
138 }
139
140 private void resizeFor(int size) {
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700141 final int oldLength = mText.length;
Adam Lesinski776abc22014-03-07 11:30:59 -0500142 if (size + 1 <= oldLength) {
143 return;
144 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145
Adam Lesinski776abc22014-03-07 11:30:59 -0500146 char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size));
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700147 System.arraycopy(mText, 0, newText, 0, mGapStart);
Adam Lesinski776abc22014-03-07 11:30:59 -0500148 final int newLength = newText.length;
149 final int delta = newLength - oldLength;
Gilles Debunne90985282012-04-17 17:08:04 -0700150 final int after = oldLength - (mGapStart + mGapLength);
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700151 System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
152 mText = newText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700154 mGapLength += delta;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 if (mGapLength < 1)
156 new Exception("mGapLength < 1").printStackTrace();
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700157
Raph Leviend7c020e2015-02-04 20:09:03 -0800158 if (mSpanCount != 0) {
159 for (int i = 0; i < mSpanCount; i++) {
160 if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
161 if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
162 }
163 calcMax(treeRoot());
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700164 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 }
166
167 private void moveGapTo(int where) {
168 if (where == mGapStart)
169 return;
170
Gilles Debunne0249b432012-04-09 16:02:31 -0700171 boolean atEnd = (where == length());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172
173 if (where < mGapStart) {
174 int overlap = mGapStart - where;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700175 System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176 } else /* where > mGapStart */ {
177 int overlap = where - mGapStart;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700178 System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 }
180
Raph Leviend7c020e2015-02-04 20:09:03 -0800181 // TODO: be more clever (although the win really isn't that big)
182 if (mSpanCount != 0) {
183 for (int i = 0; i < mSpanCount; i++) {
184 int start = mSpanStarts[i];
185 int end = mSpanEnds[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186
Raph Leviend7c020e2015-02-04 20:09:03 -0800187 if (start > mGapStart)
188 start -= mGapLength;
189 if (start > where)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 start += mGapLength;
Raph Leviend7c020e2015-02-04 20:09:03 -0800191 else if (start == where) {
192 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193
Raph Leviend7c020e2015-02-04 20:09:03 -0800194 if (flag == POINT || (atEnd && flag == PARAGRAPH))
195 start += mGapLength;
196 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197
Raph Leviend7c020e2015-02-04 20:09:03 -0800198 if (end > mGapStart)
199 end -= mGapLength;
200 if (end > where)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201 end += mGapLength;
Raph Leviend7c020e2015-02-04 20:09:03 -0800202 else if (end == where) {
203 int flag = (mSpanFlags[i] & END_MASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800204
Raph Leviend7c020e2015-02-04 20:09:03 -0800205 if (flag == POINT || (atEnd && flag == PARAGRAPH))
206 end += mGapLength;
207 }
208
209 mSpanStarts[i] = start;
210 mSpanEnds[i] = end;
211 }
212 calcMax(treeRoot());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800213 }
214
215 mGapStart = where;
216 }
217
218 // Documentation from interface
219 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
220 return replace(where, where, tb, start, end);
221 }
222
223 // Documentation from interface
224 public SpannableStringBuilder insert(int where, CharSequence tb) {
225 return replace(where, where, tb, 0, tb.length());
226 }
227
228 // Documentation from interface
229 public SpannableStringBuilder delete(int start, int end) {
230 SpannableStringBuilder ret = replace(start, end, "", 0, 0);
231
232 if (mGapLength > 2 * length())
233 resizeFor(length());
Gilles Debunne174c44c2012-04-10 16:01:09 -0700234
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 return ret; // == this
236 }
237
238 // Documentation from interface
239 public void clear() {
240 replace(0, length(), "", 0, 0);
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800241 mSpanInsertCount = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700243
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 // Documentation from interface
245 public void clearSpans() {
246 for (int i = mSpanCount - 1; i >= 0; i--) {
247 Object what = mSpans[i];
248 int ostart = mSpanStarts[i];
249 int oend = mSpanEnds[i];
250
251 if (ostart > mGapStart)
252 ostart -= mGapLength;
253 if (oend > mGapStart)
254 oend -= mGapLength;
255
256 mSpanCount = i;
257 mSpans[i] = null;
258
259 sendSpanRemoved(what, ostart, oend);
260 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800261 if (mIndexOfSpan != null) {
262 mIndexOfSpan.clear();
263 }
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800264 mSpanInsertCount = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 }
266
267 // Documentation from interface
268 public SpannableStringBuilder append(CharSequence text) {
269 int length = length();
270 return replace(length, length, text, 0, text.length());
271 }
272
Niels Egbertsca7b0272014-07-10 14:16:04 +0100273 /**
274 * Appends the character sequence {@code text} and spans {@code what} over the appended part.
275 * See {@link Spanned} for an explanation of what the flags mean.
276 * @param text the character sequence to append.
277 * @param what the object to be spanned over the appended text.
278 * @param flags see {@link Spanned}.
279 * @return this {@code SpannableStringBuilder}.
280 */
281 public SpannableStringBuilder append(CharSequence text, Object what, int flags) {
282 int start = length();
283 append(text);
284 setSpan(what, start, length(), flags);
285 return this;
286 }
287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 // Documentation from interface
289 public SpannableStringBuilder append(CharSequence text, int start, int end) {
290 int length = length();
291 return replace(length, length, text, start, end);
292 }
293
294 // Documentation from interface
295 public SpannableStringBuilder append(char text) {
296 return append(String.valueOf(text));
297 }
298
Raph Leviend7c020e2015-02-04 20:09:03 -0800299 // Returns true if a node was removed (so we can restart search from root)
300 private boolean removeSpansForChange(int start, int end, boolean textIsRemoved, int i) {
301 if ((i & 1) != 0) {
302 // internal tree node
303 if (resolveGap(mSpanMax[i]) >= start &&
304 removeSpansForChange(start, end, textIsRemoved, leftChild(i))) {
305 return true;
306 }
307 }
308 if (i < mSpanCount) {
309 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
310 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
311 mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
312 mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
313 // The following condition indicates that the span would become empty
314 (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
315 mIndexOfSpan.remove(mSpans[i]);
Clara Bayarri4e518772018-03-27 14:25:33 +0100316 removeSpan(i, 0 /* flags */);
Raph Leviend7c020e2015-02-04 20:09:03 -0800317 return true;
318 }
319 return resolveGap(mSpanStarts[i]) <= end && (i & 1) != 0 &&
320 removeSpansForChange(start, end, textIsRemoved, rightChild(i));
321 }
322 return false;
323 }
324
Gilles Debunne174c44c2012-04-10 16:01:09 -0700325 private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
326 // Can be negative
Gilles Debunne26b62d42012-04-26 18:46:19 -0700327 final int replacedLength = end - start;
328 final int replacementLength = csEnd - csStart;
329 final int nbNewChars = replacementLength - replacedLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330
Raph Leviend7c020e2015-02-04 20:09:03 -0800331 boolean changed = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 for (int i = mSpanCount - 1; i >= 0; i--) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700333 int spanStart = mSpanStarts[i];
334 if (spanStart > mGapStart)
335 spanStart -= mGapLength;
336
337 int spanEnd = mSpanEnds[i];
338 if (spanEnd > mGapStart)
339 spanEnd -= mGapLength;
340
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700342 int ost = spanStart;
343 int oen = spanEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 int clen = length();
345
Gilles Debunne174c44c2012-04-10 16:01:09 -0700346 if (spanStart > start && spanStart <= end) {
347 for (spanStart = end; spanStart < clen; spanStart++)
348 if (spanStart > end && charAt(spanStart - 1) == '\n')
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 break;
350 }
351
Gilles Debunne174c44c2012-04-10 16:01:09 -0700352 if (spanEnd > start && spanEnd <= end) {
353 for (spanEnd = end; spanEnd < clen; spanEnd++)
354 if (spanEnd > end && charAt(spanEnd - 1) == '\n')
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 break;
356 }
357
Raph Leviend7c020e2015-02-04 20:09:03 -0800358 if (spanStart != ost || spanEnd != oen) {
Siyamed Sinira6284a22017-04-10 21:42:21 -0700359 setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i],
360 true/*enforceParagraph*/);
Raph Leviend7c020e2015-02-04 20:09:03 -0800361 changed = true;
362 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700364
365 int flags = 0;
366 if (spanStart == start) flags |= SPAN_START_AT_START;
367 else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
368 if (spanEnd == start) flags |= SPAN_END_AT_START;
369 else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
370 mSpanFlags[i] |= flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800372 if (changed) {
373 restoreInvariants();
374 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800375
376 moveGapTo(end);
377
Gilles Debunne312cd582010-07-02 17:02:34 -0700378 if (nbNewChars >= mGapLength) {
379 resizeFor(mText.length + nbNewChars - mGapLength);
380 }
381
Gilles Debunnee2448682012-05-04 15:29:09 -0700382 final boolean textIsRemoved = replacementLength == 0;
Gilles Debunne90985282012-04-17 17:08:04 -0700383 // The removal pass needs to be done before the gap is updated in order to broadcast the
384 // correct previous positions to the correct intersecting SpanWatchers
Gilles Debunne26b62d42012-04-26 18:46:19 -0700385 if (replacedLength > 0) { // no need for span fixup on pure insertion
Raph Leviend7c020e2015-02-04 20:09:03 -0800386 while (mSpanCount > 0 &&
387 removeSpansForChange(start, end, textIsRemoved, treeRoot())) {
388 // keep deleting spans as needed, and restart from root after every deletion
389 // because deletion can invalidate an index.
Gilles Debunne90985282012-04-17 17:08:04 -0700390 }
391 }
392
Gilles Debunne312cd582010-07-02 17:02:34 -0700393 mGapStart += nbNewChars;
394 mGapLength -= nbNewChars;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395
396 if (mGapLength < 1)
397 new Exception("mGapLength < 1").printStackTrace();
398
Gilles Debunne174c44c2012-04-10 16:01:09 -0700399 TextUtils.getChars(cs, csStart, csEnd, mText, start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400
Gilles Debunne26b62d42012-04-26 18:46:19 -0700401 if (replacedLength > 0) { // no need for span fixup on pure insertion
Raph Leviend7c020e2015-02-04 20:09:03 -0800402 // TODO potential optimization: only update bounds on intersecting spans
Gilles Debunne90985282012-04-17 17:08:04 -0700403 final boolean atEnd = (mGapStart + mGapLength == mText.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404
Gilles Debunne90985282012-04-17 17:08:04 -0700405 for (int i = 0; i < mSpanCount; i++) {
Gilles Debunne26b62d42012-04-26 18:46:19 -0700406 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
407 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
408 atEnd, textIsRemoved);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409
Gilles Debunne26b62d42012-04-26 18:46:19 -0700410 final int endFlag = (mSpanFlags[i] & END_MASK);
411 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
412 atEnd, textIsRemoved);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800414 // TODO potential optimization: only fix up invariants when bounds actually changed
415 restoreInvariants();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800416 }
Gilles Debunne0249b432012-04-09 16:02:31 -0700417
Gilles Debunne174c44c2012-04-10 16:01:09 -0700418 if (cs instanceof Spanned) {
419 Spanned sp = (Spanned) cs;
420 Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
Gilles Debunne0249b432012-04-09 16:02:31 -0700421
422 for (int i = 0; i < spans.length; i++) {
423 int st = sp.getSpanStart(spans[i]);
424 int en = sp.getSpanEnd(spans[i]);
425
Gilles Debunne174c44c2012-04-10 16:01:09 -0700426 if (st < csStart) st = csStart;
427 if (en > csEnd) en = csEnd;
Gilles Debunne0249b432012-04-09 16:02:31 -0700428
429 // Add span only if this object is not yet used as a span in this string
Gilles Debunne90985282012-04-17 17:08:04 -0700430 if (getSpanStart(spans[i]) < 0) {
Siyamed Sinir3463c042016-01-06 13:02:59 -0800431 int copySpanStart = st - csStart + start;
432 int copySpanEnd = en - csStart + start;
433 int copySpanFlags = sp.getSpanFlags(spans[i]) | SPAN_ADDED;
434
Siyamed Sinira6284a22017-04-10 21:42:21 -0700435 setSpan(false, spans[i], copySpanStart, copySpanEnd, copySpanFlags,
436 false/*enforceParagraph*/);
Gilles Debunne0249b432012-04-09 16:02:31 -0700437 }
438 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800439 restoreInvariants();
Gilles Debunne0249b432012-04-09 16:02:31 -0700440 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 }
442
Gilles Debunne26b62d42012-04-26 18:46:19 -0700443 private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd,
444 boolean textIsRemoved) {
445 if (offset >= start && offset < mGapStart + mGapLength) {
446 if (flag == POINT) {
447 // A POINT located inside the replaced range should be moved to the end of the
448 // replaced text.
449 // The exception is when the point is at the start of the range and we are doing a
450 // text replacement (as opposed to a deletion): the point stays there.
451 if (textIsRemoved || offset > start) {
452 return mGapStart + mGapLength;
453 }
454 } else {
455 if (flag == PARAGRAPH) {
456 if (atEnd) {
457 return mGapStart + mGapLength;
458 }
459 } else { // MARK
Gilles Debunnee2448682012-05-04 15:29:09 -0700460 // MARKs should be moved to the start, with the exception of a mark located at
461 // the end of the range (which will be < mGapStart + mGapLength since mGapLength
462 // is > 0, which should stay 'unchanged' at the end of the replaced text.
Gilles Debunne26b62d42012-04-26 18:46:19 -0700463 if (textIsRemoved || offset < mGapStart - nbNewChars) {
464 return start;
465 } else {
466 // Move to the end of replaced text (needed if nbNewChars != 0)
467 return mGapStart;
468 }
469 }
470 }
471 }
472 return offset;
473 }
474
Raph Leviend7c020e2015-02-04 20:09:03 -0800475 // Note: caller is responsible for removing the mIndexOfSpan entry.
Clara Bayarri4e518772018-03-27 14:25:33 +0100476 private void removeSpan(int i, int flags) {
Gilles Debunne6435a562011-08-04 21:22:30 -0700477 Object object = mSpans[i];
478
479 int start = mSpanStarts[i];
480 int end = mSpanEnds[i];
481
482 if (start > mGapStart) start -= mGapLength;
483 if (end > mGapStart) end -= mGapLength;
484
485 int count = mSpanCount - (i + 1);
486 System.arraycopy(mSpans, i + 1, mSpans, i, count);
487 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
488 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
489 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800490 System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count);
Gilles Debunne75beb332011-04-29 11:40:22 -0700491
492 mSpanCount--;
Gilles Debunne6435a562011-08-04 21:22:30 -0700493
Raph Leviend7c020e2015-02-04 20:09:03 -0800494 invalidateIndex(i);
Gilles Debunne6435a562011-08-04 21:22:30 -0700495 mSpans[mSpanCount] = null;
496
Raph Leviend7c020e2015-02-04 20:09:03 -0800497 // Invariants must be restored before sending span removed notifications.
498 restoreInvariants();
499
Clara Bayarri4e518772018-03-27 14:25:33 +0100500 if ((flags & Spanned.SPAN_INTERMEDIATE) == 0) {
501 sendSpanRemoved(object, start, end);
502 }
Gilles Debunne75beb332011-04-29 11:40:22 -0700503 }
504
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 // Documentation from interface
506 public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
507 return replace(start, end, tb, 0, tb.length());
508 }
509
510 // Documentation from interface
Jean Chalard0a993102014-06-11 18:03:05 +0900511 public SpannableStringBuilder replace(final int start, final int end,
Gilles Debunne0249b432012-04-09 16:02:31 -0700512 CharSequence tb, int tbstart, int tbend) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700513 checkRange("replace", start, end);
514
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 int filtercount = mFilters.length;
516 for (int i = 0; i < filtercount; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -0700517 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800518
519 if (repl != null) {
520 tb = repl;
521 tbstart = 0;
522 tbend = repl.length();
523 }
524 }
525
Gilles Debunneb51036f2012-04-02 11:27:50 -0700526 final int origLen = end - start;
527 final int newLen = tbend - tbstart;
528
Gilles Debunned60da052012-04-18 15:12:20 -0700529 if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
530 // This is a no-op iif there are no spans in tb that would be added (with a 0-length)
531 // Early exit so that the text watchers do not get notified
532 return this;
533 }
534
Gilles Debunneb51036f2012-04-02 11:27:50 -0700535 TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
536 sendBeforeTextChanged(textWatchers, start, origLen, newLen);
537
Gilles Debunne0249b432012-04-09 16:02:31 -0700538 // Try to keep the cursor / selection at the same relative position during
539 // a text replacement. If replaced or replacement text length is zero, this
540 // is already taken care of.
541 boolean adjustSelection = origLen != 0 && newLen != 0;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700542 int selectionStart = 0;
543 int selectionEnd = 0;
Gilles Debunne0249b432012-04-09 16:02:31 -0700544 if (adjustSelection) {
Gilles Debunne174c44c2012-04-10 16:01:09 -0700545 selectionStart = Selection.getSelectionStart(this);
546 selectionEnd = Selection.getSelectionEnd(this);
Gilles Debunne0249b432012-04-09 16:02:31 -0700547 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548
Gilles Debunne0249b432012-04-09 16:02:31 -0700549 change(start, end, tb, tbstart, tbend);
Gilles Debunne6435a562011-08-04 21:22:30 -0700550
Gilles Debunne0249b432012-04-09 16:02:31 -0700551 if (adjustSelection) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800552 boolean changed = false;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700553 if (selectionStart > start && selectionStart < end) {
Siyamed Sinireb4df8a82016-06-03 16:22:17 -0700554 final long diff = selectionStart - start;
555 final int offset = Math.toIntExact(diff * newLen / origLen);
Gilles Debunne174c44c2012-04-10 16:01:09 -0700556 selectionStart = start + offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557
Raph Leviend7c020e2015-02-04 20:09:03 -0800558 changed = true;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700559 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
Siyamed Sinira6284a22017-04-10 21:42:21 -0700560 Spanned.SPAN_POINT_POINT, true/*enforceParagraph*/);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800561 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700562 if (selectionEnd > start && selectionEnd < end) {
Siyamed Sinireb4df8a82016-06-03 16:22:17 -0700563 final long diff = selectionEnd - start;
564 final int offset = Math.toIntExact(diff * newLen / origLen);
Gilles Debunne174c44c2012-04-10 16:01:09 -0700565 selectionEnd = start + offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566
Raph Leviend7c020e2015-02-04 20:09:03 -0800567 changed = true;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700568 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
Siyamed Sinira6284a22017-04-10 21:42:21 -0700569 Spanned.SPAN_POINT_POINT, true/*enforceParagraph*/);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800571 if (changed) {
572 restoreInvariants();
573 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 }
Gilles Debunne6435a562011-08-04 21:22:30 -0700575
Gilles Debunneb51036f2012-04-02 11:27:50 -0700576 sendTextChanged(textWatchers, start, origLen, newLen);
577 sendAfterTextChanged(textWatchers);
578
Gilles Debunne174c44c2012-04-10 16:01:09 -0700579 // Span watchers need to be called after text watchers, which may update the layout
580 sendToSpanWatchers(start, end, newLen - origLen);
581
Raph Levien051910b2014-06-15 18:25:29 -0700582 return this;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 }
584
Gilles Debunned60da052012-04-18 15:12:20 -0700585 private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
586 if (text instanceof Spanned) {
587 Spanned spanned = (Spanned) text;
588 Object[] spans = spanned.getSpans(offset, offset, Object.class);
589 final int length = spans.length;
590 for (int i = 0; i < length; i++) {
591 Object span = spans[i];
592 int flags = spanned.getSpanFlags(span);
593 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
594 }
595 }
596 return false;
597 }
598
Mathew Inwoodefeab842018-08-14 15:21:30 +0100599 @UnsupportedAppUsage
Gilles Debunne174c44c2012-04-10 16:01:09 -0700600 private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800601 for (int i = 0; i < mSpanCount; i++) {
602 int spanFlags = mSpanFlags[i];
603
604 // This loop handles only modified (not added) spans.
605 if ((spanFlags & SPAN_ADDED) != 0) continue;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700606 int spanStart = mSpanStarts[i];
607 int spanEnd = mSpanEnds[i];
608 if (spanStart > mGapStart) spanStart -= mGapLength;
609 if (spanEnd > mGapStart) spanEnd -= mGapLength;
Gilles Debunne174c44c2012-04-10 16:01:09 -0700610
611 int newReplaceEnd = replaceEnd + nbNewChars;
612 boolean spanChanged = false;
Gilles Debunne90985282012-04-17 17:08:04 -0700613
Gilles Debunne174c44c2012-04-10 16:01:09 -0700614 int previousSpanStart = spanStart;
615 if (spanStart > newReplaceEnd) {
616 if (nbNewChars != 0) {
617 previousSpanStart -= nbNewChars;
618 spanChanged = true;
619 }
620 } else if (spanStart >= replaceStart) {
621 // No change if span start was already at replace interval boundaries before replace
622 if ((spanStart != replaceStart ||
623 ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
624 (spanStart != newReplaceEnd ||
625 ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
Gilles Debunne90985282012-04-17 17:08:04 -0700626 // TODO A correct previousSpanStart cannot be computed at this point.
627 // It would require to save all the previous spans' positions before the replace
628 // Using an invalid -1 value to convey this would break the broacast range
Gilles Debunne174c44c2012-04-10 16:01:09 -0700629 spanChanged = true;
630 }
631 }
Gilles Debunne90985282012-04-17 17:08:04 -0700632
Gilles Debunne174c44c2012-04-10 16:01:09 -0700633 int previousSpanEnd = spanEnd;
634 if (spanEnd > newReplaceEnd) {
635 if (nbNewChars != 0) {
636 previousSpanEnd -= nbNewChars;
637 spanChanged = true;
638 }
639 } else if (spanEnd >= replaceStart) {
640 // No change if span start was already at replace interval boundaries before replace
641 if ((spanEnd != replaceStart ||
642 ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
643 (spanEnd != newReplaceEnd ||
644 ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
645 // TODO same as above for previousSpanEnd
646 spanChanged = true;
647 }
648 }
649
650 if (spanChanged) {
651 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
652 }
653 mSpanFlags[i] &= ~SPAN_START_END_MASK;
654 }
655
Raph Leviend7c020e2015-02-04 20:09:03 -0800656 // Handle added spans
657 for (int i = 0; i < mSpanCount; i++) {
658 int spanFlags = mSpanFlags[i];
659 if ((spanFlags & SPAN_ADDED) != 0) {
660 mSpanFlags[i] &= ~SPAN_ADDED;
661 int spanStart = mSpanStarts[i];
662 int spanEnd = mSpanEnds[i];
663 if (spanStart > mGapStart) spanStart -= mGapLength;
664 if (spanEnd > mGapStart) spanEnd -= mGapLength;
665 sendSpanAdded(mSpans[i], spanStart, spanEnd);
666 }
Gilles Debunne174c44c2012-04-10 16:01:09 -0700667 }
668 }
669
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 /**
671 * Mark the specified range of text with the specified object.
672 * The flags determine how the span will behave when text is
673 * inserted at the start or end of the span's range.
674 */
675 public void setSpan(Object what, int start, int end, int flags) {
Siyamed Sinira6284a22017-04-10 21:42:21 -0700676 setSpan(true, what, start, end, flags, true/*enforceParagraph*/);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800677 }
678
Raph Leviend7c020e2015-02-04 20:09:03 -0800679 // Note: if send is false, then it is the caller's responsibility to restore
680 // invariants. If send is false and the span already exists, then this method
681 // will not change the index of any spans.
Siyamed Sinira6284a22017-04-10 21:42:21 -0700682 private void setSpan(boolean send, Object what, int start, int end, int flags,
683 boolean enforceParagraph) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 checkRange("setSpan", start, end);
685
Gilles Debunne0249b432012-04-09 16:02:31 -0700686 int flagsStart = (flags & START_MASK) >> START_SHIFT;
Siyamed Sinira6284a22017-04-10 21:42:21 -0700687 if (isInvalidParagraph(start, flagsStart)) {
688 if (!enforceParagraph) {
689 // do not set the span
690 return;
691 }
692 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary"
693 + " (" + start + " follows " + charAt(start - 1) + ")");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 }
695
Gilles Debunne0249b432012-04-09 16:02:31 -0700696 int flagsEnd = flags & END_MASK;
Siyamed Sinira6284a22017-04-10 21:42:21 -0700697 if (isInvalidParagraph(end, flagsEnd)) {
698 if (!enforceParagraph) {
699 // do not set the span
700 return;
701 }
702 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary"
703 + " (" + end + " follows " + charAt(end - 1) + ")");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704 }
705
Gilles Debunne0249b432012-04-09 16:02:31 -0700706 // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
707 if (flagsStart == POINT && flagsEnd == MARK && start == end) {
Jean Chalard84a33202014-02-13 18:24:36 +0900708 if (send) {
709 Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
710 }
Gilles Debunne34ec2b52012-04-10 13:25:33 -0700711 // Silently ignore invalid spans when they are created from this class.
712 // This avoids the duplication of the above test code before all the
713 // calls to setSpan that are done in this class
714 return;
Gilles Debunne7c5f6702012-04-05 17:17:53 -0700715 }
716
Gilles Debunne0249b432012-04-09 16:02:31 -0700717 int nstart = start;
718 int nend = end;
719
Gilles Debunne6435a562011-08-04 21:22:30 -0700720 if (start > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800721 start += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700722 } else if (start == mGapStart) {
Gilles Debunne0249b432012-04-09 16:02:31 -0700723 if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800724 start += mGapLength;
725 }
726
Gilles Debunne6435a562011-08-04 21:22:30 -0700727 if (end > mGapStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800728 end += mGapLength;
Gilles Debunne6435a562011-08-04 21:22:30 -0700729 } else if (end == mGapStart) {
Gilles Debunne0249b432012-04-09 16:02:31 -0700730 if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731 end += mGapLength;
732 }
733
Raph Leviend7c020e2015-02-04 20:09:03 -0800734 if (mIndexOfSpan != null) {
735 Integer index = mIndexOfSpan.get(what);
736 if (index != null) {
737 int i = index;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 int ostart = mSpanStarts[i];
739 int oend = mSpanEnds[i];
740
741 if (ostart > mGapStart)
742 ostart -= mGapLength;
743 if (oend > mGapStart)
744 oend -= mGapLength;
745
746 mSpanStarts[i] = start;
747 mSpanEnds[i] = end;
748 mSpanFlags[i] = flags;
749
Raph Leviend7c020e2015-02-04 20:09:03 -0800750 if (send) {
751 restoreInvariants();
752 sendSpanChanged(what, ostart, oend, nstart, nend);
753 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754
755 return;
756 }
757 }
758
Adam Lesinski776abc22014-03-07 11:30:59 -0500759 mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
760 mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
761 mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
762 mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800763 mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount);
Raph Leviend7c020e2015-02-04 20:09:03 -0800764 invalidateIndex(mSpanCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800765 mSpanCount++;
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800766 mSpanInsertCount++;
Raph Leviend7c020e2015-02-04 20:09:03 -0800767 // Make sure there is enough room for empty interior nodes.
768 // This magic formula computes the size of the smallest perfect binary
769 // tree no smaller than mSpanCount.
770 int sizeOfMax = 2 * treeRoot() + 1;
771 if (mSpanMax.length < sizeOfMax) {
772 mSpanMax = new int[sizeOfMax];
773 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774
Raph Leviend7c020e2015-02-04 20:09:03 -0800775 if (send) {
776 restoreInvariants();
777 sendSpanAdded(what, nstart, nend);
778 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800779 }
780
Siyamed Sinira6284a22017-04-10 21:42:21 -0700781 private boolean isInvalidParagraph(int index, int flag) {
782 return flag == PARAGRAPH && index != 0 && index != length() && charAt(index - 1) != '\n';
Siyamed Sinir3463c042016-01-06 13:02:59 -0800783 }
784
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800785 /**
786 * Remove the specified markup object from the buffer.
787 */
788 public void removeSpan(Object what) {
Clara Bayarri4e518772018-03-27 14:25:33 +0100789 removeSpan(what, 0 /* flags */);
790 }
791
792 /**
793 * Remove the specified markup object from the buffer.
794 *
795 * @hide
796 */
797 public void removeSpan(Object what, int flags) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800798 if (mIndexOfSpan == null) return;
799 Integer i = mIndexOfSpan.remove(what);
800 if (i != null) {
Clara Bayarri4e518772018-03-27 14:25:33 +0100801 removeSpan(i.intValue(), flags);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 }
803 }
804
805 /**
Raph Leviend7c020e2015-02-04 20:09:03 -0800806 * Return externally visible offset given offset into gapped buffer.
807 */
808 private int resolveGap(int i) {
809 return i > mGapStart ? i - mGapLength : i;
810 }
811
812 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813 * Return the buffer offset of the beginning of the specified
814 * markup object, or -1 if it is not attached to this buffer.
815 */
816 public int getSpanStart(Object what) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800817 if (mIndexOfSpan == null) return -1;
818 Integer i = mIndexOfSpan.get(what);
819 return i == null ? -1 : resolveGap(mSpanStarts[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800820 }
821
822 /**
823 * Return the buffer offset of the end of the specified
824 * markup object, or -1 if it is not attached to this buffer.
825 */
826 public int getSpanEnd(Object what) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800827 if (mIndexOfSpan == null) return -1;
828 Integer i = mIndexOfSpan.get(what);
829 return i == null ? -1 : resolveGap(mSpanEnds[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800830 }
831
832 /**
833 * Return the flags of the end of the specified
834 * markup object, or 0 if it is not attached to this buffer.
835 */
836 public int getSpanFlags(Object what) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800837 if (mIndexOfSpan == null) return 0;
838 Integer i = mIndexOfSpan.get(what);
839 return i == null ? 0 : mSpanFlags[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 }
841
842 /**
843 * Return an array of the spans of the specified type that overlap
844 * the specified range of the buffer. The kind may be Object.class to get
845 * a list of all the spans regardless of type.
846 */
Gilles Debunne312cd582010-07-02 17:02:34 -0700847 @SuppressWarnings("unchecked")
Roozbeh Pournadercd217b82015-08-11 16:51:21 -0700848 public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) {
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800849 return getSpans(queryStart, queryEnd, kind, true);
850 }
851
852 /**
853 * Return an array of the spans of the specified type that overlap
854 * the specified range of the buffer. The kind may be Object.class to get
855 * a list of all the spans regardless of type.
856 *
857 * @param queryStart Start index.
858 * @param queryEnd End index.
859 * @param kind Class type to search for.
Siyamed Sinir1bcdcebf2016-12-14 13:24:47 -0800860 * @param sortByInsertionOrder If true the results are sorted by the insertion order.
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800861 * @param <T>
862 * @return Array of the spans. Empty array if no results are found.
863 *
864 * @hide
865 */
Mathew Inwoodefeab842018-08-14 15:21:30 +0100866 @UnsupportedAppUsage
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800867 public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind,
Siyamed Sinir1bcdcebf2016-12-14 13:24:47 -0800868 boolean sortByInsertionOrder) {
Roozbeh Pournadercd217b82015-08-11 16:51:21 -0700869 if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
870 if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
Raph Leviend7c020e2015-02-04 20:09:03 -0800871 int count = countSpans(queryStart, queryEnd, kind, treeRoot());
872 if (count == 0) {
873 return ArrayUtils.emptyArray(kind);
874 }
Gilles Debunne6435a562011-08-04 21:22:30 -0700875
Raph Leviend7c020e2015-02-04 20:09:03 -0800876 // Safe conversion, but requires a suppressWarning
877 T[] ret = (T[]) Array.newInstance(kind, count);
Siyamed Sinir1bcdcebf2016-12-14 13:24:47 -0800878 final int[] prioSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
879 final int[] orderSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
880 getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, prioSortBuffer,
881 orderSortBuffer, 0, sortByInsertionOrder);
882 if (sortByInsertionOrder) {
883 sort(ret, prioSortBuffer, orderSortBuffer);
884 recycle(prioSortBuffer);
885 recycle(orderSortBuffer);
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800886 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800887 return ret;
888 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889
Raph Leviend7c020e2015-02-04 20:09:03 -0800890 private int countSpans(int queryStart, int queryEnd, Class kind, int i) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 int count = 0;
Raph Leviend7c020e2015-02-04 20:09:03 -0800892 if ((i & 1) != 0) {
893 // internal tree node
894 int left = leftChild(i);
895 int spanMax = mSpanMax[left];
896 if (spanMax > mGapStart) {
897 spanMax -= mGapLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800899 if (spanMax >= queryStart) {
900 count = countSpans(queryStart, queryEnd, kind, left);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800901 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800902 }
903 if (i < mSpanCount) {
904 int spanStart = mSpanStarts[i];
905 if (spanStart > mGapStart) {
906 spanStart -= mGapLength;
Gilles Debunneb062e812011-09-27 14:58:37 -0700907 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800908 if (spanStart <= queryEnd) {
909 int spanEnd = mSpanEnds[i];
910 if (spanEnd > mGapStart) {
911 spanEnd -= mGapLength;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800913 if (spanEnd >= queryStart &&
914 (spanStart == spanEnd || queryStart == queryEnd ||
915 (spanStart != queryEnd && spanEnd != queryStart)) &&
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800916 (Object.class == kind || kind.isInstance(mSpans[i]))) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800917 count++;
918 }
919 if ((i & 1) != 0) {
920 count += countSpans(queryStart, queryEnd, kind, rightChild(i));
921 }
922 }
923 }
924 return count;
925 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800927 /**
928 * Fills the result array with the spans found under the current interval tree node.
929 *
930 * @param queryStart Start index for the interval query.
931 * @param queryEnd End index for the interval query.
932 * @param kind Class type to search for.
933 * @param i Index of the current tree node.
934 * @param ret Array to be filled with results.
935 * @param priority Buffer to keep record of the priorities of spans found.
936 * @param insertionOrder Buffer to keep record of the insertion orders of spans found.
937 * @param count The number of found spans.
938 * @param sort Flag to fill the priority and insertion order buffers. If false then
939 * the spans with priority flag will be sorted in the result array.
940 * @param <T>
941 * @return The total number of spans found.
942 */
Raph Leviend7c020e2015-02-04 20:09:03 -0800943 @SuppressWarnings("unchecked")
944 private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind,
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800945 int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort) {
Raph Leviend7c020e2015-02-04 20:09:03 -0800946 if ((i & 1) != 0) {
947 // internal tree node
948 int left = leftChild(i);
949 int spanMax = mSpanMax[left];
950 if (spanMax > mGapStart) {
951 spanMax -= mGapLength;
952 }
953 if (spanMax >= queryStart) {
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800954 count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority,
955 insertionOrder, count, sort);
Raph Leviend7c020e2015-02-04 20:09:03 -0800956 }
957 }
958 if (i >= mSpanCount) return count;
959 int spanStart = mSpanStarts[i];
960 if (spanStart > mGapStart) {
961 spanStart -= mGapLength;
962 }
963 if (spanStart <= queryEnd) {
964 int spanEnd = mSpanEnds[i];
965 if (spanEnd > mGapStart) {
966 spanEnd -= mGapLength;
967 }
968 if (spanEnd >= queryStart &&
969 (spanStart == spanEnd || queryStart == queryEnd ||
970 (spanStart != queryEnd && spanEnd != queryStart)) &&
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800971 (Object.class == kind || kind.isInstance(mSpans[i]))) {
972 int spanPriority = mSpanFlags[i] & SPAN_PRIORITY;
Siyamed Sinir3a789f62016-01-25 10:55:59 -0800973 int target = count;
974 if (sort) {
975 priority[target] = spanPriority;
976 insertionOrder[target] = mSpanOrder[i];
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800977 } else if (spanPriority != 0) {
978 //insertion sort for elements with priority
979 int j = 0;
980 for (; j < count; j++) {
981 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
982 if (spanPriority > p) break;
983 }
984 System.arraycopy(ret, j, ret, j + 1, count - j);
Siyamed Sinir3a789f62016-01-25 10:55:59 -0800985 target = j;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800986 }
Siyamed Sinir3a789f62016-01-25 10:55:59 -0800987 ret[target] = (T) mSpans[i];
Raph Leviend7c020e2015-02-04 20:09:03 -0800988 count++;
989 }
990 if (count < ret.length && (i & 1) != 0) {
Siyamed Sinirfa05ba02016-01-12 10:54:43 -0800991 count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority,
992 insertionOrder, count, sort);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800993 }
994 }
Raph Leviend7c020e2015-02-04 20:09:03 -0800995 return count;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800996 }
997
998 /**
Siyamed Sinir1bcdcebf2016-12-14 13:24:47 -0800999 * Obtain a temporary sort buffer.
1000 *
1001 * @param elementCount the size of the int[] to be returned
1002 * @return an int[] with elementCount length
1003 */
1004 private static int[] obtain(final int elementCount) {
1005 int[] result = null;
1006 synchronized (sCachedIntBuffer) {
1007 // try finding a tmp buffer with length of at least elementCount
1008 // if not get the first available one
1009 int candidateIndex = -1;
1010 for (int i = sCachedIntBuffer.length - 1; i >= 0; i--) {
1011 if (sCachedIntBuffer[i] != null) {
1012 if (sCachedIntBuffer[i].length >= elementCount) {
1013 candidateIndex = i;
1014 break;
1015 } else if (candidateIndex == -1) {
1016 candidateIndex = i;
1017 }
1018 }
1019 }
1020
1021 if (candidateIndex != -1) {
1022 result = sCachedIntBuffer[candidateIndex];
1023 sCachedIntBuffer[candidateIndex] = null;
1024 }
1025 }
1026 result = checkSortBuffer(result, elementCount);
1027 return result;
1028 }
1029
1030 /**
1031 * Recycle sort buffer.
1032 *
1033 * @param buffer buffer to be recycled
1034 */
1035 private static void recycle(int[] buffer) {
1036 synchronized (sCachedIntBuffer) {
1037 for (int i = 0; i < sCachedIntBuffer.length; i++) {
1038 if (sCachedIntBuffer[i] == null || buffer.length > sCachedIntBuffer[i].length) {
1039 sCachedIntBuffer[i] = buffer;
1040 break;
1041 }
1042 }
1043 }
1044 }
1045
1046 /**
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001047 * Check the size of the buffer and grow if required.
1048 *
Siyamed Sinir1bcdcebf2016-12-14 13:24:47 -08001049 * @param buffer buffer to be checked.
1050 * @param size required size.
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001051 * @return Same buffer instance if the current size is greater than required size. Otherwise a
1052 * new instance is created and returned.
1053 */
Siyamed Sinir1bcdcebf2016-12-14 13:24:47 -08001054 private static int[] checkSortBuffer(int[] buffer, int size) {
1055 if (buffer == null || size > buffer.length) {
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001056 return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size));
1057 }
1058 return buffer;
1059 }
1060
1061 /**
1062 * An iterative heap sort implementation. It will sort the spans using first their priority
1063 * then insertion order. A span with higher priority will be before a span with lower
1064 * priority. If priorities are the same, the spans will be sorted with insertion order. A
1065 * span with a lower insertion order will be before a span with a higher insertion order.
1066 *
1067 * @param array Span array to be sorted.
1068 * @param priority Priorities of the spans
1069 * @param insertionOrder Insertion orders of the spans
1070 * @param <T> Span object type.
1071 * @param <T>
1072 */
1073 private final <T> void sort(T[] array, int[] priority, int[] insertionOrder) {
1074 int size = array.length;
1075 for (int i = size / 2 - 1; i >= 0; i--) {
1076 siftDown(i, array, size, priority, insertionOrder);
1077 }
1078
1079 for (int i = size - 1; i > 0; i--) {
Siyamed Sinir051caed2016-12-14 01:32:24 -08001080 final T tmpSpan = array[0];
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001081 array[0] = array[i];
Siyamed Sinir051caed2016-12-14 01:32:24 -08001082 array[i] = tmpSpan;
1083
1084 final int tmpPriority = priority[0];
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001085 priority[0] = priority[i];
Siyamed Sinir051caed2016-12-14 01:32:24 -08001086 priority[i] = tmpPriority;
1087
1088 final int tmpOrder = insertionOrder[0];
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001089 insertionOrder[0] = insertionOrder[i];
Siyamed Sinir051caed2016-12-14 01:32:24 -08001090 insertionOrder[i] = tmpOrder;
1091
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001092 siftDown(0, array, i, priority, insertionOrder);
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001093 }
1094 }
1095
1096 /**
1097 * Helper function for heap sort.
1098 *
1099 * @param index Index of the element to sift down.
1100 * @param array Span array to be sorted.
1101 * @param size Current heap size.
1102 * @param priority Priorities of the spans
1103 * @param insertionOrder Insertion orders of the spans
1104 * @param <T> Span object type.
1105 */
1106 private final <T> void siftDown(int index, T[] array, int size, int[] priority,
1107 int[] insertionOrder) {
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001108 int left = 2 * index + 1;
1109 while (left < size) {
1110 if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) {
1111 left++;
1112 }
1113 if (compareSpans(index, left, priority, insertionOrder) >= 0) {
1114 break;
1115 }
Siyamed Sinir051caed2016-12-14 01:32:24 -08001116
1117 final T tmpSpan = array[index];
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001118 array[index] = array[left];
Siyamed Sinir051caed2016-12-14 01:32:24 -08001119 array[left] = tmpSpan;
1120
1121 final int tmpPriority = priority[index];
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001122 priority[index] = priority[left];
Siyamed Sinir051caed2016-12-14 01:32:24 -08001123 priority[left] = tmpPriority;
1124
1125 final int tmpOrder = insertionOrder[index];
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001126 insertionOrder[index] = insertionOrder[left];
Siyamed Sinir051caed2016-12-14 01:32:24 -08001127 insertionOrder[left] = tmpOrder;
1128
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001129 index = left;
1130 left = 2 * index + 1;
1131 }
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001132 }
1133
1134 /**
1135 * Compare two span elements in an array. Comparison is based first on the priority flag of
1136 * the span, and then the insertion order of the span.
1137 *
1138 * @param left Index of the element to compare.
1139 * @param right Index of the other element to compare.
1140 * @param priority Priorities of the spans
1141 * @param insertionOrder Insertion orders of the spans
1142 * @return
1143 */
1144 private final int compareSpans(int left, int right, int[] priority,
1145 int[] insertionOrder) {
1146 int priority1 = priority[left];
1147 int priority2 = priority[right];
1148 if (priority1 == priority2) {
1149 return Integer.compare(insertionOrder[left], insertionOrder[right]);
1150 }
1151 // since high priority has to be before a lower priority, the arguments to compare are
1152 // opposite of the insertion order check.
1153 return Integer.compare(priority2, priority1);
1154 }
1155
1156 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001157 * Return the next offset after <code>start</code> but less than or
1158 * equal to <code>limit</code> where a span of the specified type
1159 * begins or ends.
1160 */
1161 public int nextSpanTransition(int start, int limit, Class kind) {
Raph Leviend7c020e2015-02-04 20:09:03 -08001162 if (mSpanCount == 0) return limit;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 if (kind == null) {
1164 kind = Object.class;
1165 }
Raph Leviend7c020e2015-02-04 20:09:03 -08001166 return nextSpanTransitionRec(start, limit, kind, treeRoot());
1167 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001168
Raph Leviend7c020e2015-02-04 20:09:03 -08001169 private int nextSpanTransitionRec(int start, int limit, Class kind, int i) {
1170 if ((i & 1) != 0) {
1171 // internal tree node
1172 int left = leftChild(i);
1173 if (resolveGap(mSpanMax[left]) > start) {
1174 limit = nextSpanTransitionRec(start, limit, kind, left);
1175 }
1176 }
1177 if (i < mSpanCount) {
1178 int st = resolveGap(mSpanStarts[i]);
1179 int en = resolveGap(mSpanEnds[i]);
1180 if (st > start && st < limit && kind.isInstance(mSpans[i]))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001181 limit = st;
Raph Leviend7c020e2015-02-04 20:09:03 -08001182 if (en > start && en < limit && kind.isInstance(mSpans[i]))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001183 limit = en;
Raph Leviend7c020e2015-02-04 20:09:03 -08001184 if (st < limit && (i & 1) != 0) {
1185 limit = nextSpanTransitionRec(start, limit, kind, rightChild(i));
1186 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001187 }
1188
1189 return limit;
1190 }
1191
1192 /**
1193 * Return a new CharSequence containing a copy of the specified
1194 * range of this buffer, including the overlapping spans.
1195 */
1196 public CharSequence subSequence(int start, int end) {
1197 return new SpannableStringBuilder(this, start, end);
1198 }
1199
1200 /**
1201 * Copy the specified range of chars from this buffer into the
1202 * specified array, beginning at the specified offset.
1203 */
1204 public void getChars(int start, int end, char[] dest, int destoff) {
1205 checkRange("getChars", start, end);
1206
1207 if (end <= mGapStart) {
1208 System.arraycopy(mText, start, dest, destoff, end - start);
1209 } else if (start >= mGapStart) {
Gilles Debunne174c44c2012-04-10 16:01:09 -07001210 System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001211 } else {
1212 System.arraycopy(mText, start, dest, destoff, mGapStart - start);
1213 System.arraycopy(mText, mGapStart + mGapLength,
Gilles Debunne174c44c2012-04-10 16:01:09 -07001214 dest, destoff + (mGapStart - start),
1215 end - mGapStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 }
1217 }
1218
1219 /**
1220 * Return a String containing a copy of the chars in this buffer.
1221 */
Gilles Debunne312cd582010-07-02 17:02:34 -07001222 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001223 public String toString() {
1224 int len = length();
1225 char[] buf = new char[len];
1226
1227 getChars(0, len, buf, 0);
1228 return new String(buf);
1229 }
1230
Gilles Debunne653d3a22011-12-07 10:35:59 -08001231 /**
1232 * Return a String containing a copy of the chars in this buffer, limited to the
1233 * [start, end[ range.
1234 * @hide
1235 */
Mathew Inwoodefeab842018-08-14 15:21:30 +01001236 @UnsupportedAppUsage
Gilles Debunne653d3a22011-12-07 10:35:59 -08001237 public String substring(int start, int end) {
1238 char[] buf = new char[end - start];
1239 getChars(start, end, buf, 0);
1240 return new String(buf);
1241 }
1242
James Cookd2026682015-03-03 14:40:14 -08001243 /**
1244 * Returns the depth of TextWatcher callbacks. Returns 0 when the object is not handling
1245 * TextWatchers. A return value greater than 1 implies that a TextWatcher caused a change that
1246 * recursively triggered a TextWatcher.
1247 */
1248 public int getTextWatcherDepth() {
1249 return mTextWatcherDepth;
1250 }
1251
Gilles Debunneb51036f2012-04-02 11:27:50 -07001252 private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
1253 int n = watchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001254
James Cookd2026682015-03-03 14:40:14 -08001255 mTextWatcherDepth++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001256 for (int i = 0; i < n; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -07001257 watchers[i].beforeTextChanged(this, start, before, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001258 }
James Cookd2026682015-03-03 14:40:14 -08001259 mTextWatcherDepth--;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 }
1261
Gilles Debunneb51036f2012-04-02 11:27:50 -07001262 private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
1263 int n = watchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001264
James Cookd2026682015-03-03 14:40:14 -08001265 mTextWatcherDepth++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001266 for (int i = 0; i < n; i++) {
Gilles Debunneb51036f2012-04-02 11:27:50 -07001267 watchers[i].onTextChanged(this, start, before, after);
1268 }
James Cookd2026682015-03-03 14:40:14 -08001269 mTextWatcherDepth--;
Gilles Debunneb51036f2012-04-02 11:27:50 -07001270 }
1271
1272 private void sendAfterTextChanged(TextWatcher[] watchers) {
1273 int n = watchers.length;
1274
James Cookd2026682015-03-03 14:40:14 -08001275 mTextWatcherDepth++;
Gilles Debunneb51036f2012-04-02 11:27:50 -07001276 for (int i = 0; i < n; i++) {
1277 watchers[i].afterTextChanged(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001278 }
James Cookd2026682015-03-03 14:40:14 -08001279 mTextWatcherDepth--;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001280 }
1281
1282 private void sendSpanAdded(Object what, int start, int end) {
1283 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
1284 int n = recip.length;
1285
1286 for (int i = 0; i < n; i++) {
1287 recip[i].onSpanAdded(this, what, start, end);
1288 }
1289 }
1290
1291 private void sendSpanRemoved(Object what, int start, int end) {
1292 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
1293 int n = recip.length;
1294
1295 for (int i = 0; i < n; i++) {
1296 recip[i].onSpanRemoved(this, what, start, end);
1297 }
1298 }
1299
Gilles Debunne174c44c2012-04-10 16:01:09 -07001300 private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
1301 // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
1302 // called, so that the order of the span does not affect this broadcast.
1303 SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
1304 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
1305 int n = spanWatchers.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001306 for (int i = 0; i < n; i++) {
Gilles Debunne174c44c2012-04-10 16:01:09 -07001307 spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001308 }
1309 }
1310
1311 private static String region(int start, int end) {
1312 return "(" + start + " ... " + end + ")";
1313 }
1314
1315 private void checkRange(final String operation, int start, int end) {
1316 if (end < start) {
1317 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001318 region(start, end) + " has end before start");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001319 }
1320
1321 int len = length();
1322
1323 if (start > len || end > len) {
1324 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001325 region(start, end) + " ends beyond length " + len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001326 }
1327
1328 if (start < 0 || end < 0) {
1329 throw new IndexOutOfBoundsException(operation + " " +
Gilles Debunne174c44c2012-04-10 16:01:09 -07001330 region(start, end) + " starts before 0");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001331 }
1332 }
1333
Gilles Debunne174c44c2012-04-10 16:01:09 -07001334 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335 private boolean isprint(char c) { // XXX
1336 if (c >= ' ' && c <= '~')
1337 return true;
1338 else
1339 return false;
1340 }
1341
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001342 private static final int startFlag(int flag) {
1343 return (flag >> 4) & 0x0F;
1344 }
1345
1346 private static final int endFlag(int flag) {
1347 return flag & 0x0F;
1348 }
1349
1350 public void dump() { // XXX
1351 for (int i = 0; i < mGapStart; i++) {
1352 System.out.print('|');
1353 System.out.print(' ');
1354 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1355 System.out.print(' ');
1356 }
1357
1358 for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
1359 System.out.print('|');
1360 System.out.print('(');
1361 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1362 System.out.print(')');
1363 }
1364
1365 for (int i = mGapStart + mGapLength; i < mText.length; i++) {
1366 System.out.print('|');
1367 System.out.print(' ');
1368 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1369 System.out.print(' ');
1370 }
1371
1372 System.out.print('\n');
1373
1374 for (int i = 0; i < mText.length + 1; i++) {
1375 int found = 0;
1376 int wfound = 0;
1377
1378 for (int j = 0; j < mSpanCount; j++) {
1379 if (mSpanStarts[j] == i) {
1380 found = 1;
1381 wfound = j;
1382 break;
1383 }
1384
1385 if (mSpanEnds[j] == i) {
1386 found = 2;
1387 wfound = j;
1388 break;
1389 }
1390 }
1391
1392 if (found == 1) {
1393 if (startFlag(mSpanFlags[wfound]) == MARK)
1394 System.out.print("( ");
1395 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1396 System.out.print("< ");
1397 else
1398 System.out.print("[ ");
1399 } else if (found == 2) {
1400 if (endFlag(mSpanFlags[wfound]) == POINT)
1401 System.out.print(") ");
1402 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1403 System.out.print("> ");
1404 else
1405 System.out.print("] ");
1406 } else {
1407 System.out.print(" ");
1408 }
1409 }
1410
1411 System.out.print("\n");
1412 }
Gilles Debunne26b62d42012-04-26 18:46:19 -07001413 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001414
1415 /**
1416 * Don't call this yourself -- exists for Canvas to use internally.
1417 * {@hide}
1418 */
John Reckcaa08ff2016-10-07 13:21:36 -07001419 @Override
1420 public void drawText(BaseCanvas c, int start, int end, float x, float y, Paint p) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001421 checkRange("drawText", start, end);
1422
1423 if (end <= mGapStart) {
1424 c.drawText(mText, start, end - start, x, y, p);
1425 } else if (start >= mGapStart) {
1426 c.drawText(mText, start + mGapLength, end - start, x, y, p);
1427 } else {
1428 char[] buf = TextUtils.obtain(end - start);
1429
1430 getChars(start, end, buf, 0);
1431 c.drawText(buf, 0, end - start, x, y, p);
1432 TextUtils.recycle(buf);
1433 }
1434 }
1435
Doug Felt0c702b82010-05-14 10:55:42 -07001436
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001437 /**
Doug Feltf47d7402010-04-21 16:01:52 -07001438 * Don't call this yourself -- exists for Canvas to use internally.
1439 * {@hide}
1440 */
John Reckcaa08ff2016-10-07 13:21:36 -07001441 @Override
1442 public void drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd,
Raph Levien051910b2014-06-15 18:25:29 -07001443 float x, float y, boolean isRtl, Paint p) {
Doug Feltf47d7402010-04-21 16:01:52 -07001444 checkRange("drawTextRun", start, end);
1445
Doug Felt0c702b82010-05-14 10:55:42 -07001446 int contextLen = contextEnd - contextStart;
1447 int len = end - start;
1448 if (contextEnd <= mGapStart) {
Raph Levien051910b2014-06-15 18:25:29 -07001449 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p);
Doug Felt0c702b82010-05-14 10:55:42 -07001450 } else if (contextStart >= mGapStart) {
1451 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
Raph Levien051910b2014-06-15 18:25:29 -07001452 contextLen, x, y, isRtl, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001453 } else {
Doug Felt0c702b82010-05-14 10:55:42 -07001454 char[] buf = TextUtils.obtain(contextLen);
1455 getChars(contextStart, contextEnd, buf, 0);
Raph Levien051910b2014-06-15 18:25:29 -07001456 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p);
Doug Feltf47d7402010-04-21 16:01:52 -07001457 TextUtils.recycle(buf);
1458 }
1459 }
1460
Gilles Debunne174c44c2012-04-10 16:01:09 -07001461 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001462 * Don't call this yourself -- exists for Paint to use internally.
1463 * {@hide}
1464 */
1465 public float measureText(int start, int end, Paint p) {
1466 checkRange("measureText", start, end);
1467
1468 float ret;
1469
1470 if (end <= mGapStart) {
1471 ret = p.measureText(mText, start, end - start);
1472 } else if (start >= mGapStart) {
1473 ret = p.measureText(mText, start + mGapLength, end - start);
1474 } else {
1475 char[] buf = TextUtils.obtain(end - start);
1476
1477 getChars(start, end, buf, 0);
1478 ret = p.measureText(buf, 0, end - start);
1479 TextUtils.recycle(buf);
1480 }
1481
1482 return ret;
1483 }
1484
1485 /**
1486 * Don't call this yourself -- exists for Paint to use internally.
1487 * {@hide}
1488 */
1489 public int getTextWidths(int start, int end, float[] widths, Paint p) {
1490 checkRange("getTextWidths", start, end);
1491
1492 int ret;
1493
1494 if (end <= mGapStart) {
1495 ret = p.getTextWidths(mText, start, end - start, widths);
1496 } else if (start >= mGapStart) {
Gilles Debunne174c44c2012-04-10 16:01:09 -07001497 ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001498 } else {
1499 char[] buf = TextUtils.obtain(end - start);
1500
1501 getChars(start, end, buf, 0);
1502 ret = p.getTextWidths(buf, 0, end - start, widths);
1503 TextUtils.recycle(buf);
1504 }
1505
1506 return ret;
1507 }
1508
Doug Felt0c702b82010-05-14 10:55:42 -07001509 /**
1510 * Don't call this yourself -- exists for Paint to use internally.
1511 * {@hide}
1512 */
Raph Levien051910b2014-06-15 18:25:29 -07001513 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl,
Doug Felt0c702b82010-05-14 10:55:42 -07001514 float[] advances, int advancesPos, Paint p) {
1515
1516 float ret;
1517
1518 int contextLen = contextEnd - contextStart;
1519 int len = end - start;
1520
1521 if (end <= mGapStart) {
1522 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
Raph Levien051910b2014-06-15 18:25:29 -07001523 isRtl, advances, advancesPos);
Doug Felt0c702b82010-05-14 10:55:42 -07001524 } else if (start >= mGapStart) {
1525 ret = p.getTextRunAdvances(mText, start + mGapLength, len,
Raph Levien051910b2014-06-15 18:25:29 -07001526 contextStart + mGapLength, contextLen, isRtl, advances, advancesPos);
Doug Felt0c702b82010-05-14 10:55:42 -07001527 } else {
1528 char[] buf = TextUtils.obtain(contextLen);
1529 getChars(contextStart, contextEnd, buf, 0);
1530 ret = p.getTextRunAdvances(buf, start - contextStart, len,
Raph Levien051910b2014-06-15 18:25:29 -07001531 0, contextLen, isRtl, advances, advancesPos);
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001532 TextUtils.recycle(buf);
1533 }
1534
1535 return ret;
1536 }
1537
1538 /**
Gilles Debunnef09d5102011-03-02 17:28:31 -08001539 * Returns the next cursor position in the run. This avoids placing the cursor between
1540 * surrogates, between characters that form conjuncts, between base characters and combining
1541 * marks, or within a reordering cluster.
1542 *
1543 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
1544 * span enclosing the cursor in the direction of movement.
1545 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
1546 * the start of the string.</p>
1547 *
Gilles Debunne616f3832011-03-02 19:50:16 -08001548 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
Gilles Debunnef09d5102011-03-02 17:28:31 -08001549 * this returns -1. Otherwise this will never return a value before contextStart or after
1550 * contextEnd.</p>
1551 *
1552 * @param contextStart the start index of the context
1553 * @param contextEnd the (non-inclusive) end index of the context
Seigo Nonakafa95b832018-09-17 14:09:25 -07001554 * @param dir 1 if the run is RTL, otherwise 0
Gilles Debunnef09d5102011-03-02 17:28:31 -08001555 * @param offset the cursor position to move from
Gilles Debunne616f3832011-03-02 19:50:16 -08001556 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
1557 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
1558 * CURSOR_AT_OR_BEFORE, or CURSOR_AT
Gilles Debunnef09d5102011-03-02 17:28:31 -08001559 * @param p the Paint object that is requesting this information
1560 * @return the offset of the next position, or -1
Gilles Debunneb0b22562011-03-03 14:51:39 -08001561 * @deprecated This is an internal method, refrain from using it in your code
Gilles Debunnef09d5102011-03-02 17:28:31 -08001562 */
Gilles Debunneb0b22562011-03-03 14:51:39 -08001563 @Deprecated
Raph Levien051910b2014-06-15 18:25:29 -07001564 public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07001565 int cursorOpt, Paint p) {
Seigo Nonakafa95b832018-09-17 14:09:25 -07001566 return getTextRunCursor(contextStart, contextEnd, dir == 1, offset, cursorOpt, p);
1567 }
1568
1569 /** @hide */
1570 @Override
1571 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset,
1572 int cursorOpt, Paint p) {
Doug Felt0c702b82010-05-14 10:55:42 -07001573
1574 int ret;
1575
1576 int contextLen = contextEnd - contextStart;
1577 if (contextEnd <= mGapStart) {
1578 ret = p.getTextRunCursor(mText, contextStart, contextLen,
Seigo Nonakafa95b832018-09-17 14:09:25 -07001579 isRtl, offset, cursorOpt);
Doug Felt0c702b82010-05-14 10:55:42 -07001580 } else if (contextStart >= mGapStart) {
1581 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
Seigo Nonakafa95b832018-09-17 14:09:25 -07001582 isRtl, offset + mGapLength, cursorOpt) - mGapLength;
Doug Felt0c702b82010-05-14 10:55:42 -07001583 } else {
1584 char[] buf = TextUtils.obtain(contextLen);
1585 getChars(contextStart, contextEnd, buf, 0);
1586 ret = p.getTextRunCursor(buf, 0, contextLen,
Seigo Nonakafa95b832018-09-17 14:09:25 -07001587 isRtl, offset - contextStart, cursorOpt) + contextStart;
Doug Felt0c702b82010-05-14 10:55:42 -07001588 TextUtils.recycle(buf);
1589 }
1590
1591 return ret;
1592 }
1593
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001594 // Documentation from interface
1595 public void setFilters(InputFilter[] filters) {
1596 if (filters == null) {
1597 throw new IllegalArgumentException();
1598 }
1599
1600 mFilters = filters;
1601 }
1602
1603 // Documentation from interface
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001604 public InputFilter[] getFilters() {
1605 return mFilters;
1606 }
1607
Chet Haase9b985722013-09-18 15:12:35 -07001608 // Same as SpannableStringInternal
1609 @Override
1610 public boolean equals(Object o) {
1611 if (o instanceof Spanned &&
1612 toString().equals(o.toString())) {
Haoyu Zhang6b4380e2018-09-14 14:18:20 -07001613 final Spanned other = (Spanned) o;
Chet Haase9b985722013-09-18 15:12:35 -07001614 // Check span data
Haoyu Zhang6b4380e2018-09-14 14:18:20 -07001615 final Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
1616 final Object[] thisSpans = getSpans(0, length(), Object.class);
Chet Haase9b985722013-09-18 15:12:35 -07001617 if (mSpanCount == otherSpans.length) {
1618 for (int i = 0; i < mSpanCount; ++i) {
Haoyu Zhang6b4380e2018-09-14 14:18:20 -07001619 final Object thisSpan = thisSpans[i];
1620 final Object otherSpan = otherSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -07001621 if (thisSpan == this) {
1622 if (other != otherSpan ||
1623 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
1624 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
1625 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
1626 return false;
1627 }
1628 } else if (!thisSpan.equals(otherSpan) ||
1629 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
1630 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
1631 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
Chet Haase9b985722013-09-18 15:12:35 -07001632 return false;
1633 }
1634 }
1635 return true;
1636 }
Chet Haase9b985722013-09-18 15:12:35 -07001637 }
1638 return false;
1639 }
1640
1641 // Same as SpannableStringInternal
1642 @Override
1643 public int hashCode() {
1644 int hash = toString().hashCode();
1645 hash = hash * 31 + mSpanCount;
1646 for (int i = 0; i < mSpanCount; ++i) {
1647 Object span = mSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -07001648 if (span != this) {
1649 hash = hash * 31 + span.hashCode();
1650 }
Chet Haase9b985722013-09-18 15:12:35 -07001651 hash = hash * 31 + getSpanStart(span);
1652 hash = hash * 31 + getSpanEnd(span);
1653 hash = hash * 31 + getSpanFlags(span);
1654 }
1655 return hash;
1656 }
1657
Raph Leviend7c020e2015-02-04 20:09:03 -08001658 // Primitives for treating span list as binary tree
1659
1660 // The spans (along with start and end offsets and flags) are stored in linear arrays sorted
1661 // by start offset. For fast searching, there is a binary search structure imposed over these
1662 // arrays. This structure is inorder traversal of a perfect binary tree, a slightly unusual
1663 // but advantageous approach.
1664
1665 // The value-containing nodes are indexed 0 <= i < n (where n = mSpanCount), thus preserving
1666 // logic that accesses the values as a contiguous array. Other balanced binary tree approaches
1667 // (such as a complete binary tree) would require some shuffling of node indices.
1668
1669 // Basic properties of this structure: For a perfect binary tree of height m:
1670 // The tree has 2^(m+1) - 1 total nodes.
1671 // The root of the tree has index 2^m - 1.
1672 // All leaf nodes have even index, all interior nodes odd.
1673 // The height of a node of index i is the number of trailing ones in i's binary representation.
1674 // The left child of a node i of height h is i - 2^(h - 1).
1675 // The right child of a node i of height h is i + 2^(h - 1).
1676
1677 // Note that for arbitrary n, interior nodes of this tree may be >= n. Thus, the general
1678 // structure of a recursive traversal of node i is:
1679 // * traverse left child if i is an interior node
1680 // * process i if i < n
1681 // * traverse right child if i is an interior node and i < n
1682
1683 private int treeRoot() {
1684 return Integer.highestOneBit(mSpanCount) - 1;
1685 }
1686
1687 // (i+1) & ~i is equal to 2^(the number of trailing ones in i)
1688 private static int leftChild(int i) {
1689 return i - (((i + 1) & ~i) >> 1);
1690 }
1691
1692 private static int rightChild(int i) {
1693 return i + (((i + 1) & ~i) >> 1);
1694 }
1695
1696 // The span arrays are also augmented by an mSpanMax[] array that represents an interval tree
1697 // over the binary tree structure described above. For each node, the mSpanMax[] array contains
1698 // the maximum value of mSpanEnds of that node and its descendants. Thus, traversals can
1699 // easily reject subtrees that contain no spans overlapping the area of interest.
1700
1701 // Note that mSpanMax[] also has a valid valuefor interior nodes of index >= n, but which have
1702 // descendants of index < n. In these cases, it simply represents the maximum span end of its
1703 // descendants. This is a consequence of the perfect binary tree structure.
1704 private int calcMax(int i) {
1705 int max = 0;
1706 if ((i & 1) != 0) {
1707 // internal tree node
1708 max = calcMax(leftChild(i));
1709 }
1710 if (i < mSpanCount) {
1711 max = Math.max(max, mSpanEnds[i]);
1712 if ((i & 1) != 0) {
1713 max = Math.max(max, calcMax(rightChild(i)));
1714 }
1715 }
1716 mSpanMax[i] = max;
1717 return max;
1718 }
1719
1720 // restores binary interval tree invariants after any mutation of span structure
1721 private void restoreInvariants() {
1722 if (mSpanCount == 0) return;
1723
1724 // invariant 1: span starts are nondecreasing
1725
1726 // This is a simple insertion sort because we expect it to be mostly sorted.
1727 for (int i = 1; i < mSpanCount; i++) {
1728 if (mSpanStarts[i] < mSpanStarts[i - 1]) {
1729 Object span = mSpans[i];
1730 int start = mSpanStarts[i];
1731 int end = mSpanEnds[i];
1732 int flags = mSpanFlags[i];
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001733 int insertionOrder = mSpanOrder[i];
Raph Leviend7c020e2015-02-04 20:09:03 -08001734 int j = i;
1735 do {
1736 mSpans[j] = mSpans[j - 1];
1737 mSpanStarts[j] = mSpanStarts[j - 1];
1738 mSpanEnds[j] = mSpanEnds[j - 1];
1739 mSpanFlags[j] = mSpanFlags[j - 1];
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001740 mSpanOrder[j] = mSpanOrder[j - 1];
Raph Leviend7c020e2015-02-04 20:09:03 -08001741 j--;
1742 } while (j > 0 && start < mSpanStarts[j - 1]);
1743 mSpans[j] = span;
1744 mSpanStarts[j] = start;
1745 mSpanEnds[j] = end;
1746 mSpanFlags[j] = flags;
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001747 mSpanOrder[j] = insertionOrder;
Raph Leviend7c020e2015-02-04 20:09:03 -08001748 invalidateIndex(j);
1749 }
1750 }
1751
1752 // invariant 2: max is max span end for each node and its descendants
1753 calcMax(treeRoot());
1754
1755 // invariant 3: mIndexOfSpan maps spans back to indices
1756 if (mIndexOfSpan == null) {
1757 mIndexOfSpan = new IdentityHashMap<Object, Integer>();
1758 }
1759 for (int i = mLowWaterMark; i < mSpanCount; i++) {
1760 Integer existing = mIndexOfSpan.get(mSpans[i]);
1761 if (existing == null || existing != i) {
1762 mIndexOfSpan.put(mSpans[i], i);
1763 }
1764 }
1765 mLowWaterMark = Integer.MAX_VALUE;
1766 }
1767
1768 // Call this on any update to mSpans[], so that mIndexOfSpan can be updated
1769 private void invalidateIndex(int i) {
1770 mLowWaterMark = Math.min(i, mLowWaterMark);
1771 }
1772
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001773 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
Siyamed Sinir1bcdcebf2016-12-14 13:24:47 -08001774
1775 @GuardedBy("sCachedIntBuffer")
1776 private static final int[][] sCachedIntBuffer = new int[6][0];
1777
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001778 private InputFilter[] mFilters = NO_FILTERS;
1779
Mathew Inwoodefeab842018-08-14 15:21:30 +01001780 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001781 private char[] mText;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001782 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001783 private int mGapStart;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001784 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001785 private int mGapLength;
1786
Mathew Inwoodefeab842018-08-14 15:21:30 +01001787 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001788 private Object[] mSpans;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001789 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001790 private int[] mSpanStarts;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001791 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001792 private int[] mSpanEnds;
Raph Leviend7c020e2015-02-04 20:09:03 -08001793 private int[] mSpanMax; // see calcMax() for an explanation of what this array stores
Mathew Inwoodefeab842018-08-14 15:21:30 +01001794 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001795 private int[] mSpanFlags;
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001796 private int[] mSpanOrder; // store the order of span insertion
1797 private int mSpanInsertCount; // counter for the span insertion
Siyamed Sinirfa05ba02016-01-12 10:54:43 -08001798
Mathew Inwoodefeab842018-08-14 15:21:30 +01001799 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001800 private int mSpanCount;
Raph Leviend7c020e2015-02-04 20:09:03 -08001801 private IdentityHashMap<Object, Integer> mIndexOfSpan;
1802 private int mLowWaterMark; // indices below this have not been touched
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001803
James Cookd2026682015-03-03 14:40:14 -08001804 // TextWatcher callbacks may trigger changes that trigger more callbacks. This keeps track of
1805 // how deep the callbacks go.
1806 private int mTextWatcherDepth;
1807
Gilles Debunneb51036f2012-04-02 11:27:50 -07001808 // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
Gilles Debunne0249b432012-04-09 16:02:31 -07001809 private static final int MARK = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001810 private static final int POINT = 2;
1811 private static final int PARAGRAPH = 3;
1812
1813 private static final int START_MASK = 0xF0;
1814 private static final int END_MASK = 0x0F;
1815 private static final int START_SHIFT = 4;
Gilles Debunne174c44c2012-04-10 16:01:09 -07001816
1817 // These bits are not (currently) used by SPANNED flags
Raph Leviend7c020e2015-02-04 20:09:03 -08001818 private static final int SPAN_ADDED = 0x800;
Gilles Debunne174c44c2012-04-10 16:01:09 -07001819 private static final int SPAN_START_AT_START = 0x1000;
1820 private static final int SPAN_START_AT_END = 0x2000;
1821 private static final int SPAN_END_AT_START = 0x4000;
1822 private static final int SPAN_END_AT_END = 0x8000;
1823 private static final int SPAN_START_END_MASK = 0xF000;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001824}