Make SpannableStringBuilder.getSpans thread-safe

Add cached int buffers for sorting.

Test: All CtsTextTestCases

Merged-In: Ice0b3c3fffc541b26aca94c93fd01e30e13efe2e

Bug: 33609774
Change-Id: Ib728654a384cfc072d1c190611e4553aa8957fa8
This commit is contained in:
Siyamed Sinir
2016-12-14 13:24:47 -08:00
parent 683ea24d6e
commit 4234144a2b

View File

@@ -21,6 +21,7 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -73,8 +74,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
mSpanFlags = EmptyArray.INT;
mSpanMax = EmptyArray.INT;
mSpanOrder = EmptyArray.INT;
mPrioSortBuffer = EmptyArray.INT;
mOrderSortBuffer = EmptyArray.INT;
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
@@ -856,14 +855,14 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
* @param queryStart Start index.
* @param queryEnd End index.
* @param kind Class type to search for.
* @param sort If true the results are sorted by the insertion order.
* @param sortByInsertionOrder If true the results are sorted by the insertion order.
* @param <T>
* @return Array of the spans. Empty array if no results are found.
*
* @hide
*/
public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind,
boolean sort) {
boolean sortByInsertionOrder) {
if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
int count = countSpans(queryStart, queryEnd, kind, treeRoot());
@@ -873,13 +872,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
// Safe conversion, but requires a suppressWarning
T[] ret = (T[]) Array.newInstance(kind, count);
if (sort) {
mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count);
mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count);
final int[] prioSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
final int[] orderSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, prioSortBuffer,
orderSortBuffer, 0, sortByInsertionOrder);
if (sortByInsertionOrder) {
sort(ret, prioSortBuffer, orderSortBuffer);
recycle(prioSortBuffer);
recycle(orderSortBuffer);
}
getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer,
mOrderSortBuffer, 0, sort);
if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer);
return ret;
}
@@ -991,16 +992,64 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
return count;
}
/**
* Obtain a temporary sort buffer.
*
* @param elementCount the size of the int[] to be returned
* @return an int[] with elementCount length
*/
private static int[] obtain(final int elementCount) {
int[] result = null;
synchronized (sCachedIntBuffer) {
// try finding a tmp buffer with length of at least elementCount
// if not get the first available one
int candidateIndex = -1;
for (int i = sCachedIntBuffer.length - 1; i >= 0; i--) {
if (sCachedIntBuffer[i] != null) {
if (sCachedIntBuffer[i].length >= elementCount) {
candidateIndex = i;
break;
} else if (candidateIndex == -1) {
candidateIndex = i;
}
}
}
if (candidateIndex != -1) {
result = sCachedIntBuffer[candidateIndex];
sCachedIntBuffer[candidateIndex] = null;
}
}
result = checkSortBuffer(result, elementCount);
return result;
}
/**
* Recycle sort buffer.
*
* @param buffer buffer to be recycled
*/
private static void recycle(int[] buffer) {
synchronized (sCachedIntBuffer) {
for (int i = 0; i < sCachedIntBuffer.length; i++) {
if (sCachedIntBuffer[i] == null || buffer.length > sCachedIntBuffer[i].length) {
sCachedIntBuffer[i] = buffer;
break;
}
}
}
}
/**
* Check the size of the buffer and grow if required.
*
* @param buffer Buffer to be checked.
* @param size Required size.
* @param buffer buffer to be checked.
* @param size required size.
* @return Same buffer instance if the current size is greater than required size. Otherwise a
* new instance is created and returned.
*/
private final int[] checkSortBuffer(int[] buffer, int size) {
if(size > buffer.length) {
private static int[] checkSortBuffer(int[] buffer, int size) {
if (buffer == null || size > buffer.length) {
return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size));
}
return buffer;
@@ -1702,6 +1751,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
@GuardedBy("sCachedIntBuffer")
private static final int[][] sCachedIntBuffer = new int[6][0];
private InputFilter[] mFilters = NO_FILTERS;
private char[] mText;
@@ -1715,8 +1768,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
private int[] mSpanFlags;
private int[] mSpanOrder; // store the order of span insertion
private int mSpanInsertCount; // counter for the span insertion
private int[] mPrioSortBuffer; // buffer used to sort getSpans result
private int[] mOrderSortBuffer; // buffer used to sort getSpans result
private int mSpanCount;
private IdentityHashMap<Object, Integer> mIndexOfSpan;