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