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.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 */ 43 public SpannableStringBuilder() { 44 this(""); 45 } 46 47 /** 48 * Create a new SpannableStringBuilder containing a copy of the 49 * specified text, including its spans if any. 50 */ 51 public SpannableStringBuilder(CharSequence text) { 52 this(text, 0, text.length()); 53 } 54 55 /** 56 * Create a new SpannableStringBuilder containing a copy of the 57 * specified slice of the specified text, including its spans if any. 58 */ 59 public SpannableStringBuilder(CharSequence text, int start, int end) { 60 int srclen = end - start; 61 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 108 public static SpannableStringBuilder valueOf(CharSequence source) { 109 if (source instanceof SpannableStringBuilder) { 110 return (SpannableStringBuilder) source; 111 } else { 112 return new SpannableStringBuilder(source); 113 } 114 } 115 116 /** 117 * Return the char at the specified offset within the buffer. 118 */ 119 public char charAt(int where) { 120 int len = length(); 121 if (where < 0) { 122 throw new IndexOutOfBoundsException("charAt: " + where + " < 0"); 123 } else if (where >= len) { 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 */ 136 public int length() { 137 return mText.length - mGapLength; 138 } 139 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 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 219 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) { 220 return replace(where, where, tb, start, end); 221 } 222 223 // Documentation from interface 224 public SpannableStringBuilder insert(int where, CharSequence tb) { 225 return replace(where, where, tb, 0, tb.length()); 226 } 227 228 // Documentation from interface 229 public SpannableStringBuilder delete(int start, int end) { 230 SpannableStringBuilder ret = replace(start, end, "", 0, 0); 231 232 if (mGapLength > 2 * length()) 233 resizeFor(length()); 234 235 return ret; // == this 236 } 237 238 // Documentation from interface 239 public void clear() { 240 replace(0, length(), "", 0, 0); 241 mSpanInsertCount = 0; 242 } 243 244 // Documentation from interface 245 public void clearSpans() { 246 for (int i = mSpanCount - 1; i >= 0; i--) { 247 Object what = mSpans[i]; 248 int ostart = mSpanStarts[i]; 249 int oend = mSpanEnds[i]; 250 251 if (ostart > mGapStart) 252 ostart -= mGapLength; 253 if (oend > mGapStart) 254 oend -= mGapLength; 255 256 mSpanCount = i; 257 mSpans[i] = null; 258 259 sendSpanRemoved(what, ostart, oend); 260 } 261 if (mIndexOfSpan != null) { 262 mIndexOfSpan.clear(); 263 } 264 mSpanInsertCount = 0; 265 } 266 267 // Documentation from interface 268 public SpannableStringBuilder append(CharSequence text) { 269 int length = length(); 270 return replace(length, length, text, 0, text.length()); 271 } 272 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 */ 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 289 public SpannableStringBuilder append(CharSequence text, int start, int end) { 290 int length = length(); 291 return replace(length, length, text, start, end); 292 } 293 294 // Documentation from interface 295 public SpannableStringBuilder append(char text) { 296 return append(String.valueOf(text)); 297 } 298 299 // Returns true if a node was removed (so we can restart search from root) 300 private boolean removeSpansForChange(int start, int end, boolean textIsRemoved, int i) { 301 if ((i & 1) != 0) { 302 // internal tree node 303 if (resolveGap(mSpanMax[i]) >= start && 304 removeSpansForChange(start, end, textIsRemoved, leftChild(i))) { 305 return true; 306 } 307 } 308 if (i < mSpanCount) { 309 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == 310 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE && 311 mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength && 312 mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength && 313 // The following condition indicates that the span would become empty 314 (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) { 315 mIndexOfSpan.remove(mSpans[i]); 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 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 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. 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 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 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 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 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 */ 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. 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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") 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 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 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") 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 */ 1004 private static int[] obtain(final int elementCount) { 1005 int[] result = null; 1006 synchronized (sCachedIntBuffer) { 1007 // try finding a tmp buffer with length of at least elementCount 1008 // if not get the first available one 1009 int candidateIndex = -1; 1010 for (int i = sCachedIntBuffer.length - 1; i >= 0; i--) { 1011 if (sCachedIntBuffer[i] != null) { 1012 if (sCachedIntBuffer[i].length >= elementCount) { 1013 candidateIndex = i; 1014 break; 1015 } else if (candidateIndex == -1) { 1016 candidateIndex = i; 1017 } 1018 } 1019 } 1020 1021 if (candidateIndex != -1) { 1022 result = sCachedIntBuffer[candidateIndex]; 1023 sCachedIntBuffer[candidateIndex] = null; 1024 } 1025 } 1026 result = checkSortBuffer(result, elementCount); 1027 return result; 1028 } 1029 1030 /** 1031 * Recycle sort buffer. 1032 * 1033 * @param buffer buffer to be recycled 1034 */ 1035 private static void recycle(int[] buffer) { 1036 synchronized (sCachedIntBuffer) { 1037 for (int i = 0; i < sCachedIntBuffer.length; i++) { 1038 if (sCachedIntBuffer[i] == null || buffer.length > sCachedIntBuffer[i].length) { 1039 sCachedIntBuffer[i] = buffer; 1040 break; 1041 } 1042 } 1043 } 1044 } 1045 1046 /** 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 1196 public CharSequence subSequence(int start, int end) { 1197 return new SpannableStringBuilder(this, start, end); 1198 } 1199 1200 /** 1201 * Copy the specified range of chars from this buffer into the 1202 * specified array, beginning at the specified offset. 1203 */ 1204 public void getChars(int start, int end, char[] dest, int destoff) { 1205 checkRange("getChars", start, end); 1206 1207 if (end <= mGapStart) { 1208 System.arraycopy(mText, start, dest, destoff, end - start); 1209 } else if (start >= mGapStart) { 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 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 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 */ 1248 public int getTextWatcherDepth() { 1249 return mTextWatcherDepth; 1250 } 1251 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 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 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 1282 private void sendSpanAdded(Object what, int start, int end) { 1283 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 1284 int n = recip.length; 1285 1286 for (int i = 0; i < n; i++) { 1287 recip[i].onSpanAdded(this, what, start, end); 1288 } 1289 } 1290 1291 private void sendSpanRemoved(Object what, int start, int end) { 1292 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 1293 int n = recip.length; 1294 1295 for (int i = 0; i < n; i++) { 1296 recip[i].onSpanRemoved(this, what, start, end); 1297 } 1298 } 1299 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 1311 private static String region(int start, int end) { 1312 return "(" + start + " ... " + end + ")"; 1313 } 1314 1315 private void checkRange(final String operation, int start, int end) { 1316 if (end < start) { 1317 throw new IndexOutOfBoundsException(operation + " " + 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 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 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 */ 1465 public float measureText(int start, int end, Paint p) { 1466 checkRange("measureText", start, end); 1467 1468 float ret; 1469 1470 if (end <= mGapStart) { 1471 ret = p.measureText(mText, start, end - start); 1472 } else if (start >= mGapStart) { 1473 ret = p.measureText(mText, start + mGapLength, end - start); 1474 } else { 1475 char[] buf = TextUtils.obtain(end - start); 1476 1477 getChars(start, end, buf, 0); 1478 ret = p.measureText(buf, 0, end - start); 1479 TextUtils.recycle(buf); 1480 } 1481 1482 return ret; 1483 } 1484 1485 /** 1486 * Don't call this yourself -- exists for Paint to use internally. 1487 * {@hide} 1488 */ 1489 public int getTextWidths(int start, int end, float[] widths, Paint p) { 1490 checkRange("getTextWidths", start, end); 1491 1492 int ret; 1493 1494 if (end <= mGapStart) { 1495 ret = p.getTextWidths(mText, start, end - start, widths); 1496 } else if (start >= mGapStart) { 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 */ 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 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 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 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 1604 public InputFilter[] getFilters() { 1605 return mFilters; 1606 } 1607 1608 // Same as SpannableStringInternal 1609 @Override 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 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 1683 private int treeRoot() { 1684 return Integer.highestOneBit(mSpanCount) - 1; 1685 } 1686 1687 // (i+1) & ~i is equal to 2^(the number of trailing ones in i) 1688 private static int leftChild(int i) { 1689 return i - (((i + 1) & ~i) >> 1); 1690 } 1691 1692 private static int rightChild(int i) { 1693 return i + (((i + 1) & ~i) >> 1); 1694 } 1695 1696 // The span arrays are also augmented by an mSpanMax[] array that represents an interval tree 1697 // over the binary tree structure described above. For each node, the mSpanMax[] array contains 1698 // the maximum value of mSpanEnds of that node and its descendants. Thus, traversals can 1699 // easily reject subtrees that contain no spans overlapping the area of interest. 1700 1701 // Note that mSpanMax[] also has a valid valuefor interior nodes of index >= n, but which have 1702 // descendants of index < n. In these cases, it simply represents the maximum span end of its 1703 // descendants. This is a consequence of the perfect binary tree structure. 1704 private int calcMax(int i) { 1705 int max = 0; 1706 if ((i & 1) != 0) { 1707 // internal tree node 1708 max = calcMax(leftChild(i)); 1709 } 1710 if (i < mSpanCount) { 1711 max = Math.max(max, mSpanEnds[i]); 1712 if ((i & 1) != 0) { 1713 max = Math.max(max, calcMax(rightChild(i))); 1714 } 1715 } 1716 mSpanMax[i] = max; 1717 return max; 1718 } 1719 1720 // restores binary interval tree invariants after any mutation of span structure 1721 private void restoreInvariants() { 1722 if (mSpanCount == 0) return; 1723 1724 // invariant 1: span starts are nondecreasing 1725 1726 // This is a simple insertion sort because we expect it to be mostly sorted. 1727 for (int i = 1; i < mSpanCount; i++) { 1728 if (mSpanStarts[i] < mSpanStarts[i - 1]) { 1729 Object span = mSpans[i]; 1730 int start = mSpanStarts[i]; 1731 int end = mSpanEnds[i]; 1732 int flags = mSpanFlags[i]; 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 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