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