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