Skip to content

Commit 9098528

Browse files
author
Gilles Debunne
committed
Fixed SSB. Correct broadcast of removed spans' positions
Found while tracking bug 6326750 A bug in the SpannableStringBuilderSpanTest JUnit CTS test was hiding this problem. Also removed the instanceof test on SpanWatcher. All spans, including those implementing this interface should be copied. Change-Id: I5233818fb0c08ab56477720db932a5be453e88ee
1 parent d7f256d commit 9098528

File tree

1 file changed

+35
-17
lines changed

1 file changed

+35
-17
lines changed

core/java/android/text/SpannableStringBuilder.java

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,15 @@ public int length() {
130130
private void resizeFor(int size) {
131131
final int oldLength = mText.length;
132132
final int newLength = ArrayUtils.idealCharArraySize(size + 1);
133-
final int after = oldLength - (mGapStart + mGapLength);
133+
final int delta = newLength - oldLength;
134+
if (delta == 0) return;
134135

135136
char[] newText = new char[newLength];
136137
System.arraycopy(mText, 0, newText, 0, mGapStart);
138+
final int after = oldLength - (mGapStart + mGapLength);
137139
System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
138140
mText = newText;
139141

140-
final int delta = newLength - oldLength;
141142
mGapLength += delta;
142143
if (mGapLength < 1)
143144
new Exception("mGapLength < 1").printStackTrace();
@@ -305,6 +306,26 @@ private void change(int start, int end, CharSequence cs, int csStart, int csEnd)
305306
resizeFor(mText.length + nbNewChars - mGapLength);
306307
}
307308

309+
// The removal pass needs to be done before the gap is updated in order to broadcast the
310+
// correct previous positions to the correct intersecting SpanWatchers
311+
if (end > start) { // no need for span fixup on pure insertion
312+
// A for loop will not work because the array is being modified
313+
// Do not iterate in reverse to keep the SpanWatchers notified in ordering
314+
// Also, a removed SpanWatcher should not get notified of removed spans located
315+
// further in the span array.
316+
int i = 0;
317+
while (i < mSpanCount) {
318+
if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
319+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
320+
mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
321+
mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) {
322+
removeSpan(i);
323+
} else {
324+
i++;
325+
}
326+
}
327+
}
328+
308329
mGapStart += nbNewChars;
309330
mGapLength -= nbNewChars;
310331

@@ -313,11 +334,10 @@ private void change(int start, int end, CharSequence cs, int csStart, int csEnd)
313334

314335
TextUtils.getChars(cs, csStart, csEnd, mText, start);
315336

316-
if (end > start) {
317-
// no need for span fixup on pure insertion
318-
boolean atEnd = (mGapStart + mGapLength == mText.length);
337+
if (end > start) { // no need for span fixup on pure insertion
338+
final boolean atEnd = (mGapStart + mGapLength == mText.length);
319339

320-
for (int i = mSpanCount - 1; i >= 0; i--) {
340+
for (int i = 0; i < mSpanCount; i++) {
321341
if (mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength) {
322342
int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
323343

@@ -331,16 +351,11 @@ private void change(int start, int end, CharSequence cs, int csStart, int csEnd)
331351
if (mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) {
332352
int flag = (mSpanFlags[i] & END_MASK);
333353

334-
if (flag == POINT || (flag == PARAGRAPH && atEnd))
354+
if (flag == POINT || (flag == PARAGRAPH && atEnd)) {
335355
mSpanEnds[i] = mGapStart + mGapLength;
336-
else
356+
} else {
337357
mSpanEnds[i] = start;
338-
}
339-
340-
// remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE, which are POINT_MARK and could
341-
// get their boundaries swapped by the above code
342-
if (mSpanEnds[i] < mSpanStarts[i]) {
343-
removeSpan(i);
358+
}
344359
}
345360
}
346361
}
@@ -359,7 +374,7 @@ private void change(int start, int end, CharSequence cs, int csStart, int csEnd)
359374
if (en > csEnd) en = csEnd;
360375

361376
// Add span only if this object is not yet used as a span in this string
362-
if (getSpanStart(spans[i]) < 0 && !(spans[i] instanceof SpanWatcher)) {
377+
if (getSpanStart(spans[i]) < 0) {
363378
setSpan(false, spans[i], st - csStart + start, en - csStart + start,
364379
sp.getSpanFlags(spans[i]));
365380
}
@@ -465,6 +480,7 @@ private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars
465480

466481
int newReplaceEnd = replaceEnd + nbNewChars;
467482
boolean spanChanged = false;
483+
468484
int previousSpanStart = spanStart;
469485
if (spanStart > newReplaceEnd) {
470486
if (nbNewChars != 0) {
@@ -477,11 +493,13 @@ private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars
477493
((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
478494
(spanStart != newReplaceEnd ||
479495
((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
480-
// TODO previousSpanStart is incorrect, but we would need to save all the
481-
// previous spans' positions before replace to provide it
496+
// TODO A correct previousSpanStart cannot be computed at this point.
497+
// It would require to save all the previous spans' positions before the replace
498+
// Using an invalid -1 value to convey this would break the broacast range
482499
spanChanged = true;
483500
}
484501
}
502+
485503
int previousSpanEnd = spanEnd;
486504
if (spanEnd > newReplaceEnd) {
487505
if (nbNewChars != 0) {

0 commit comments

Comments
 (0)