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