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