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.os.Build; 22 23 import com.android.internal.util.ArrayUtils; 24 import com.android.internal.util.GrowingArrayUtils; 25 26 import libcore.util.EmptyArray; 27 28 import java.lang.reflect.Array; 29 30 /* package */ abstract class SpannableStringInternal 31 { SpannableStringInternal(CharSequence source, int start, int end, boolean ignoreNoCopySpan)32 /* package */ SpannableStringInternal(CharSequence source, 33 int start, int end, boolean ignoreNoCopySpan) { 34 if (start == 0 && end == source.length()) 35 mText = source.toString(); 36 else 37 mText = source.toString().substring(start, end); 38 39 mSpans = EmptyArray.OBJECT; 40 // Invariant: mSpanData.length = mSpans.length * COLUMNS 41 mSpanData = EmptyArray.INT; 42 43 if (source instanceof Spanned) { 44 if (source instanceof SpannableStringInternal) { 45 copySpansFromInternal( 46 (SpannableStringInternal) source, start, end, ignoreNoCopySpan); 47 } else { 48 copySpansFromSpanned((Spanned) source, start, end, ignoreNoCopySpan); 49 } 50 } 51 } 52 53 /** 54 * This unused method is left since this is listed in hidden api list. 55 * 56 * Due to backward compatibility reasons, we copy even NoCopySpan by default 57 */ 58 @UnsupportedAppUsage SpannableStringInternal(CharSequence source, int start, int end)59 /* package */ SpannableStringInternal(CharSequence source, int start, int end) { 60 this(source, start, end, false /* ignoreNoCopySpan */); 61 } 62 63 /** 64 * Copies another {@link Spanned} object's spans between [start, end] into this object. 65 * 66 * @param src Source object to copy from. 67 * @param start Start index in the source object. 68 * @param end End index in the source object. 69 * @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source} 70 */ copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan)71 private void copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan) { 72 Object[] spans = src.getSpans(start, end, Object.class); 73 74 for (int i = 0; i < spans.length; i++) { 75 if (ignoreNoCopySpan && spans[i] instanceof NoCopySpan) { 76 continue; 77 } 78 int st = src.getSpanStart(spans[i]); 79 int en = src.getSpanEnd(spans[i]); 80 int fl = src.getSpanFlags(spans[i]); 81 82 if (st < start) 83 st = start; 84 if (en > end) 85 en = end; 86 87 setSpan(spans[i], st - start, en - start, fl, false/*enforceParagraph*/); 88 } 89 } 90 91 /** 92 * Copies a {@link SpannableStringInternal} object's spans between [start, end] into this 93 * object. 94 * 95 * @param src Source object to copy from. 96 * @param start Start index in the source object. 97 * @param end End index in the source object. 98 * @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons. 99 */ copySpansFromInternal(SpannableStringInternal src, int start, int end, boolean ignoreNoCopySpan)100 private void copySpansFromInternal(SpannableStringInternal src, int start, int end, 101 boolean ignoreNoCopySpan) { 102 int count = 0; 103 final int[] srcData = src.mSpanData; 104 final Object[] srcSpans = src.mSpans; 105 final int limit = src.mSpanCount; 106 boolean hasNoCopySpan = false; 107 108 for (int i = 0; i < limit; i++) { 109 int spanStart = srcData[i * COLUMNS + START]; 110 int spanEnd = srcData[i * COLUMNS + END]; 111 if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue; 112 if (srcSpans[i] instanceof NoCopySpan) { 113 hasNoCopySpan = true; 114 if (ignoreNoCopySpan) { 115 continue; 116 } 117 } 118 count++; 119 } 120 121 if (count == 0) return; 122 123 if (!hasNoCopySpan && start == 0 && end == src.length()) { 124 mSpans = ArrayUtils.newUnpaddedObjectArray(src.mSpans.length); 125 mSpanData = new int[src.mSpanData.length]; 126 mSpanCount = src.mSpanCount; 127 System.arraycopy(src.mSpans, 0, mSpans, 0, src.mSpans.length); 128 System.arraycopy(src.mSpanData, 0, mSpanData, 0, mSpanData.length); 129 } else { 130 mSpanCount = count; 131 mSpans = ArrayUtils.newUnpaddedObjectArray(mSpanCount); 132 mSpanData = new int[mSpans.length * COLUMNS]; 133 for (int i = 0, j = 0; i < limit; i++) { 134 int spanStart = srcData[i * COLUMNS + START]; 135 int spanEnd = srcData[i * COLUMNS + END]; 136 if (isOutOfCopyRange(start, end, spanStart, spanEnd) 137 || (ignoreNoCopySpan && srcSpans[i] instanceof NoCopySpan)) { 138 continue; 139 } 140 if (spanStart < start) spanStart = start; 141 if (spanEnd > end) spanEnd = end; 142 143 mSpans[j] = srcSpans[i]; 144 mSpanData[j * COLUMNS + START] = spanStart - start; 145 mSpanData[j * COLUMNS + END] = spanEnd - start; 146 mSpanData[j * COLUMNS + FLAGS] = srcData[i * COLUMNS + FLAGS]; 147 j++; 148 } 149 } 150 } 151 152 /** 153 * Checks if [spanStart, spanEnd] interval is excluded from [start, end]. 154 * 155 * @return True if excluded, false if included. 156 */ 157 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) isOutOfCopyRange(int start, int end, int spanStart, int spanEnd)158 private final boolean isOutOfCopyRange(int start, int end, int spanStart, int spanEnd) { 159 if (spanStart > end || spanEnd < start) return true; 160 if (spanStart != spanEnd && start != end) { 161 if (spanStart == end || spanEnd == start) return true; 162 } 163 return false; 164 } 165 length()166 public final int length() { 167 return mText.length(); 168 } 169 charAt(int i)170 public final char charAt(int i) { 171 return mText.charAt(i); 172 } 173 toString()174 public final String toString() { 175 return mText; 176 } 177 178 /* subclasses must do subSequence() to preserve type */ 179 getChars(int start, int end, char[] dest, int off)180 public final void getChars(int start, int end, char[] dest, int off) { 181 mText.getChars(start, end, dest, off); 182 } 183 184 @UnsupportedAppUsage setSpan(Object what, int start, int end, int flags)185 /* package */ void setSpan(Object what, int start, int end, int flags) { 186 setSpan(what, start, end, flags, true/*enforceParagraph*/); 187 } 188 189 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) isIndexFollowsNextLine(int index)190 private boolean isIndexFollowsNextLine(int index) { 191 return index != 0 && index != length() && charAt(index - 1) != '\n'; 192 } 193 194 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setSpan(Object what, int start, int end, int flags, boolean enforceParagraph)195 private void setSpan(Object what, int start, int end, int flags, boolean enforceParagraph) { 196 int nstart = start; 197 int nend = end; 198 199 checkRange("setSpan", start, end); 200 201 if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) { 202 if (isIndexFollowsNextLine(start)) { 203 if (!enforceParagraph) { 204 // do not set the span 205 return; 206 } 207 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary" 208 + " (" + start + " follows " + charAt(start - 1) + ")"); 209 } 210 211 if (isIndexFollowsNextLine(end)) { 212 if (!enforceParagraph) { 213 // do not set the span 214 return; 215 } 216 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary" 217 + " (" + end + " follows " + charAt(end - 1) + ")"); 218 } 219 } 220 221 int count = mSpanCount; 222 Object[] spans = mSpans; 223 int[] data = mSpanData; 224 225 for (int i = 0; i < count; i++) { 226 if (spans[i] == what) { 227 int ostart = data[i * COLUMNS + START]; 228 int oend = data[i * COLUMNS + END]; 229 230 data[i * COLUMNS + START] = start; 231 data[i * COLUMNS + END] = end; 232 data[i * COLUMNS + FLAGS] = flags; 233 234 sendSpanChanged(what, ostart, oend, nstart, nend); 235 return; 236 } 237 } 238 239 if (mSpanCount + 1 >= mSpans.length) { 240 Object[] newtags = ArrayUtils.newUnpaddedObjectArray( 241 GrowingArrayUtils.growSize(mSpanCount)); 242 int[] newdata = new int[newtags.length * 3]; 243 244 System.arraycopy(mSpans, 0, newtags, 0, mSpanCount); 245 System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3); 246 247 mSpans = newtags; 248 mSpanData = newdata; 249 } 250 251 mSpans[mSpanCount] = what; 252 mSpanData[mSpanCount * COLUMNS + START] = start; 253 mSpanData[mSpanCount * COLUMNS + END] = end; 254 mSpanData[mSpanCount * COLUMNS + FLAGS] = flags; 255 mSpanCount++; 256 257 if (this instanceof Spannable) 258 sendSpanAdded(what, nstart, nend); 259 } 260 261 @UnsupportedAppUsage removeSpan(Object what)262 /* package */ void removeSpan(Object what) { 263 removeSpan(what, 0 /* flags */); 264 } 265 266 /** 267 * @hide 268 */ removeSpan(Object what, int flags)269 public void removeSpan(Object what, int flags) { 270 int count = mSpanCount; 271 Object[] spans = mSpans; 272 int[] data = mSpanData; 273 274 for (int i = count - 1; i >= 0; i--) { 275 if (spans[i] == what) { 276 int ostart = data[i * COLUMNS + START]; 277 int oend = data[i * COLUMNS + END]; 278 279 int c = count - (i + 1); 280 281 System.arraycopy(spans, i + 1, spans, i, c); 282 System.arraycopy(data, (i + 1) * COLUMNS, 283 data, i * COLUMNS, c * COLUMNS); 284 285 mSpanCount--; 286 287 if ((flags & Spanned.SPAN_INTERMEDIATE) == 0) { 288 sendSpanRemoved(what, ostart, oend); 289 } 290 return; 291 } 292 } 293 } 294 295 @UnsupportedAppUsage getSpanStart(Object what)296 public int getSpanStart(Object what) { 297 int count = mSpanCount; 298 Object[] spans = mSpans; 299 int[] data = mSpanData; 300 301 for (int i = count - 1; i >= 0; i--) { 302 if (spans[i] == what) { 303 return data[i * COLUMNS + START]; 304 } 305 } 306 307 return -1; 308 } 309 310 @UnsupportedAppUsage getSpanEnd(Object what)311 public int getSpanEnd(Object what) { 312 int count = mSpanCount; 313 Object[] spans = mSpans; 314 int[] data = mSpanData; 315 316 for (int i = count - 1; i >= 0; i--) { 317 if (spans[i] == what) { 318 return data[i * COLUMNS + END]; 319 } 320 } 321 322 return -1; 323 } 324 325 @UnsupportedAppUsage getSpanFlags(Object what)326 public int getSpanFlags(Object what) { 327 int count = mSpanCount; 328 Object[] spans = mSpans; 329 int[] data = mSpanData; 330 331 for (int i = count - 1; i >= 0; i--) { 332 if (spans[i] == what) { 333 return data[i * COLUMNS + FLAGS]; 334 } 335 } 336 337 return 0; 338 } 339 340 @UnsupportedAppUsage getSpans(int queryStart, int queryEnd, Class<T> kind)341 public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) { 342 int count = 0; 343 344 int spanCount = mSpanCount; 345 Object[] spans = mSpans; 346 int[] data = mSpanData; 347 Object[] ret = null; 348 Object ret1 = null; 349 350 for (int i = 0; i < spanCount; i++) { 351 int spanStart = data[i * COLUMNS + START]; 352 int spanEnd = data[i * COLUMNS + END]; 353 354 if (spanStart > queryEnd) { 355 continue; 356 } 357 if (spanEnd < queryStart) { 358 continue; 359 } 360 361 if (spanStart != spanEnd && queryStart != queryEnd) { 362 if (spanStart == queryEnd) { 363 continue; 364 } 365 if (spanEnd == queryStart) { 366 continue; 367 } 368 } 369 370 // verify span class as late as possible, since it is expensive 371 if (kind != null && kind != Object.class && !kind.isInstance(spans[i])) { 372 continue; 373 } 374 375 if (count == 0) { 376 ret1 = spans[i]; 377 count++; 378 } else { 379 if (count == 1) { 380 ret = (Object[]) Array.newInstance(kind, spanCount - i + 1); 381 ret[0] = ret1; 382 } 383 384 int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY; 385 if (prio != 0) { 386 int j; 387 388 for (j = 0; j < count; j++) { 389 int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY; 390 391 if (prio > p) { 392 break; 393 } 394 } 395 396 System.arraycopy(ret, j, ret, j + 1, count - j); 397 ret[j] = spans[i]; 398 count++; 399 } else { 400 ret[count++] = spans[i]; 401 } 402 } 403 } 404 405 if (count == 0) { 406 return (T[]) ArrayUtils.emptyArray(kind); 407 } 408 if (count == 1) { 409 ret = (Object[]) Array.newInstance(kind, 1); 410 ret[0] = ret1; 411 return (T[]) ret; 412 } 413 if (count == ret.length) { 414 return (T[]) ret; 415 } 416 417 Object[] nret = (Object[]) Array.newInstance(kind, count); 418 System.arraycopy(ret, 0, nret, 0, count); 419 return (T[]) nret; 420 } 421 422 @UnsupportedAppUsage nextSpanTransition(int start, int limit, Class kind)423 public int nextSpanTransition(int start, int limit, Class kind) { 424 int count = mSpanCount; 425 Object[] spans = mSpans; 426 int[] data = mSpanData; 427 428 if (kind == null) { 429 kind = Object.class; 430 } 431 432 for (int i = 0; i < count; i++) { 433 int st = data[i * COLUMNS + START]; 434 int en = data[i * COLUMNS + END]; 435 436 if (st > start && st < limit && kind.isInstance(spans[i])) 437 limit = st; 438 if (en > start && en < limit && kind.isInstance(spans[i])) 439 limit = en; 440 } 441 442 return limit; 443 } 444 445 @UnsupportedAppUsage sendSpanAdded(Object what, int start, int end)446 private void sendSpanAdded(Object what, int start, int end) { 447 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 448 int n = recip.length; 449 450 for (int i = 0; i < n; i++) { 451 recip[i].onSpanAdded((Spannable) this, what, start, end); 452 } 453 } 454 455 @UnsupportedAppUsage sendSpanRemoved(Object what, int start, int end)456 private void sendSpanRemoved(Object what, int start, int end) { 457 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 458 int n = recip.length; 459 460 for (int i = 0; i < n; i++) { 461 recip[i].onSpanRemoved((Spannable) this, what, start, end); 462 } 463 } 464 465 @UnsupportedAppUsage sendSpanChanged(Object what, int s, int e, int st, int en)466 private void sendSpanChanged(Object what, int s, int e, int st, int en) { 467 SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), 468 SpanWatcher.class); 469 int n = recip.length; 470 471 for (int i = 0; i < n; i++) { 472 recip[i].onSpanChanged((Spannable) this, what, s, e, st, en); 473 } 474 } 475 476 @UnsupportedAppUsage region(int start, int end)477 private static String region(int start, int end) { 478 return "(" + start + " ... " + end + ")"; 479 } 480 481 @UnsupportedAppUsage checkRange(final String operation, int start, int end)482 private void checkRange(final String operation, int start, int end) { 483 if (end < start) { 484 throw new IndexOutOfBoundsException(operation + " " + 485 region(start, end) + 486 " has end before start"); 487 } 488 489 int len = length(); 490 491 if (start > len || end > len) { 492 throw new IndexOutOfBoundsException(operation + " " + 493 region(start, end) + 494 " ends beyond length " + len); 495 } 496 497 if (start < 0 || end < 0) { 498 throw new IndexOutOfBoundsException(operation + " " + 499 region(start, end) + 500 " starts before 0"); 501 } 502 } 503 504 // Same as SpannableStringBuilder 505 @Override equals(@ullable Object o)506 public boolean equals(@Nullable Object o) { 507 if (o instanceof Spanned && 508 toString().equals(o.toString())) { 509 final Spanned other = (Spanned) o; 510 // Check span data 511 final Object[] otherSpans = other.getSpans(0, other.length(), Object.class); 512 final Object[] thisSpans = getSpans(0, length(), Object.class); 513 if (mSpanCount == otherSpans.length) { 514 for (int i = 0; i < mSpanCount; ++i) { 515 final Object thisSpan = thisSpans[i]; 516 final Object otherSpan = otherSpans[i]; 517 if (thisSpan == this) { 518 if (other != otherSpan || 519 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || 520 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || 521 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { 522 return false; 523 } 524 } else if (!thisSpan.equals(otherSpan) || 525 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || 526 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || 527 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { 528 return false; 529 } 530 } 531 return true; 532 } 533 } 534 return false; 535 } 536 537 // Same as SpannableStringBuilder 538 @Override hashCode()539 public int hashCode() { 540 int hash = toString().hashCode(); 541 hash = hash * 31 + mSpanCount; 542 for (int i = 0; i < mSpanCount; ++i) { 543 Object span = mSpans[i]; 544 if (span != this) { 545 hash = hash * 31 + span.hashCode(); 546 } 547 hash = hash * 31 + getSpanStart(span); 548 hash = hash * 31 + getSpanEnd(span); 549 hash = hash * 31 + getSpanFlags(span); 550 } 551 return hash; 552 } 553 554 /** 555 * Following two unused methods are left since these are listed in hidden api list. 556 * 557 * Due to backward compatibility reasons, we copy even NoCopySpan by default 558 */ 559 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) copySpans(Spanned src, int start, int end)560 private void copySpans(Spanned src, int start, int end) { 561 copySpansFromSpanned(src, start, end, false); 562 } 563 564 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) copySpans(SpannableStringInternal src, int start, int end)565 private void copySpans(SpannableStringInternal src, int start, int end) { 566 copySpansFromInternal(src, start, end, false); 567 } 568 569 570 571 @UnsupportedAppUsage 572 private String mText; 573 @UnsupportedAppUsage 574 private Object[] mSpans; 575 @UnsupportedAppUsage 576 private int[] mSpanData; 577 @UnsupportedAppUsage 578 private int mSpanCount; 579 580 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 581 /* package */ static final Object[] EMPTY = new Object[0]; 582 583 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 584 private static final int START = 0; 585 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 586 private static final int END = 1; 587 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 588 private static final int FLAGS = 2; 589 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 590 private static final int COLUMNS = 3; 591 } 592