Merge "Introduce new constructor for not copying NoCopySpan" into pi-dev

am: 54ff524a2b

Change-Id: I2e11e189c240df026c478bb9d5a4cd97c5e30e9e
This commit is contained in:
Seigo Nonaka
2018-03-21 03:52:08 +00:00
committed by android-build-merger
5 changed files with 411 additions and 25 deletions

View File

@@ -16,7 +16,6 @@
package android.text;
/**
* This is the class for text whose content is immutable but to which
* markup objects can be attached and detached.
@@ -26,12 +25,27 @@ public class SpannableString
extends SpannableStringInternal
implements CharSequence, GetChars, Spannable
{
/**
* @param source source object to copy from
* @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source}
* @hide
*/
public SpannableString(CharSequence source, boolean ignoreNoCopySpan) {
super(source, 0, source.length(), ignoreNoCopySpan);
}
/**
* For the backward compatibility reasons, this constructor copies all spans including {@link
* android.text.NoCopySpan}.
* @param source source text
*/
public SpannableString(CharSequence source) {
super(source, 0, source.length());
this(source, false /* ignoreNoCopySpan */); // preserve existing NoCopySpan behavior
}
private SpannableString(CharSequence source, int start, int end) {
super(source, start, end);
// preserve existing NoCopySpan behavior
super(source, start, end, false /* ignoreNoCopySpan */);
}
public static SpannableString valueOf(CharSequence source) {

View File

@@ -26,7 +26,7 @@ import java.lang.reflect.Array;
/* package */ abstract class SpannableStringInternal
{
/* package */ SpannableStringInternal(CharSequence source,
int start, int end) {
int start, int end, boolean ignoreNoCopySpan) {
if (start == 0 && end == source.length())
mText = source.toString();
else
@@ -38,24 +38,37 @@ import java.lang.reflect.Array;
if (source instanceof Spanned) {
if (source instanceof SpannableStringInternal) {
copySpans((SpannableStringInternal) source, start, end);
copySpans((SpannableStringInternal) source, start, end, ignoreNoCopySpan);
} else {
copySpans((Spanned) source, start, end);
copySpans((Spanned) source, start, end, ignoreNoCopySpan);
}
}
}
/**
* This unused method is left since this is listed in hidden api list.
*
* Due to backward compatibility reasons, we copy even NoCopySpan by default
*/
/* package */ SpannableStringInternal(CharSequence source, int start, int end) {
this(source, start, end, false /* ignoreNoCopySpan */);
}
/**
* Copies another {@link Spanned} object's spans between [start, end] into this object.
*
* @param src Source object to copy from.
* @param start Start index in the source object.
* @param end End index in the source object.
* @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source}
*/
private final void copySpans(Spanned src, int start, int end) {
private void copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
Object[] spans = src.getSpans(start, end, Object.class);
for (int i = 0; i < spans.length; i++) {
if (ignoreNoCopySpan && spans[i] instanceof NoCopySpan) {
continue;
}
int st = src.getSpanStart(spans[i]);
int en = src.getSpanEnd(spans[i]);
int fl = src.getSpanFlags(spans[i]);
@@ -76,35 +89,48 @@ import java.lang.reflect.Array;
* @param src Source object to copy from.
* @param start Start index in the source object.
* @param end End index in the source object.
* @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons.
*/
private final void copySpans(SpannableStringInternal src, int start, int end) {
if (start == 0 && end == src.length()) {
private void copySpans(SpannableStringInternal src, int start, int end,
boolean ignoreNoCopySpan) {
int count = 0;
final int[] srcData = src.mSpanData;
final Object[] srcSpans = src.mSpans;
final int limit = src.mSpanCount;
boolean hasNoCopySpan = false;
for (int i = 0; i < limit; i++) {
int spanStart = srcData[i * COLUMNS + START];
int spanEnd = srcData[i * COLUMNS + END];
if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
if (srcSpans[i] instanceof NoCopySpan) {
hasNoCopySpan = true;
if (ignoreNoCopySpan) {
continue;
}
}
count++;
}
if (count == 0) return;
if (!hasNoCopySpan && start == 0 && end == src.length()) {
mSpans = ArrayUtils.newUnpaddedObjectArray(src.mSpans.length);
mSpanData = new int[src.mSpanData.length];
mSpanCount = src.mSpanCount;
System.arraycopy(src.mSpans, 0, mSpans, 0, src.mSpans.length);
System.arraycopy(src.mSpanData, 0, mSpanData, 0, mSpanData.length);
} else {
int count = 0;
int[] srcData = src.mSpanData;
int limit = src.mSpanCount;
for (int i = 0; i < limit; i++) {
int spanStart = srcData[i * COLUMNS + START];
int spanEnd = srcData[i * COLUMNS + END];
if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
count++;
}
if (count == 0) return;
Object[] srcSpans = src.mSpans;
mSpanCount = count;
mSpans = ArrayUtils.newUnpaddedObjectArray(mSpanCount);
mSpanData = new int[mSpans.length * COLUMNS];
for (int i = 0, j = 0; i < limit; i++) {
int spanStart = srcData[i * COLUMNS + START];
int spanEnd = srcData[i * COLUMNS + END];
if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
if (isOutOfCopyRange(start, end, spanStart, spanEnd)
|| (ignoreNoCopySpan && srcSpans[i] instanceof NoCopySpan)) {
continue;
}
if (spanStart < start) spanStart = start;
if (spanEnd > end) spanEnd = end;
@@ -494,6 +520,21 @@ import java.lang.reflect.Array;
return hash;
}
/**
* Following two unused methods are left since these are listed in hidden api list.
*
* Due to backward compatibility reasons, we copy even NoCopySpan by default
*/
private void copySpans(Spanned src, int start, int end) {
copySpans(src, start, end, false);
}
private void copySpans(SpannableStringInternal src, int start, int end) {
copySpans(src, start, end, false);
}
private String mText;
private Object[] mSpans;
private int[] mSpanData;

View File

@@ -26,12 +26,27 @@ public final class SpannedString
extends SpannableStringInternal
implements CharSequence, GetChars, Spanned
{
/**
* @param source source object to copy from
* @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source}
* @hide
*/
public SpannedString(CharSequence source, boolean ignoreNoCopySpan) {
super(source, 0, source.length(), ignoreNoCopySpan);
}
/**
* For the backward compatibility reasons, this constructor copies all spans including {@link
* android.text.NoCopySpan}.
* @param source source text
*/
public SpannedString(CharSequence source) {
super(source, 0, source.length());
this(source, false /* ignoreNoCopySpan */); // preserve existing NoCopySpan behavior
}
private SpannedString(CharSequence source, int start, int end) {
super(source, start, end);
// preserve existing NoCopySpan behavior
super(source, start, end, false /* ignoreNoCopySpan */);
}
public CharSequence subSequence(int start, int end) {

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.text;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import android.annotation.NonNull;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.style.QuoteSpan;
import android.text.style.UnderlineSpan;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SpannableStringNoCopyTest {
@Test
public void testCopyConstructor_copyNoCopySpans_SpannableStringInternalImpl() {
final SpannableString first = new SpannableString("t\nest data");
first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// By default, copy NoCopySpans
final SpannedString copied = new SpannedString(first);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(3, spans.length);
}
@Test
public void testCopyConstructor_doesNotCopyNoCopySpans_SpannableStringInternalImpl() {
final SpannableString first = new SpannableString("t\nest data");
first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// Do not copy NoCopySpan if specified so.
final SpannedString copied = new SpannedString(first, false /* copyNoCopySpan */);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(2, spans.length);
for (int i = 0; i < spans.length; i++) {
assertFalse(spans[i] instanceof NoCopySpan);
}
}
@Test
public void testCopyConstructor_copyNoCopySpans_OtherSpannableImpl() {
final SpannableString first = new SpannableString("t\nest data");
first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// By default, copy NoCopySpans
final SpannedString copied = new SpannedString(new CustomSpannable(first));
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(3, spans.length);
}
@Test
public void testCopyConstructor_doesNotCopyNoCopySpans_OtherSpannableImpl() {
final SpannableString first = new SpannableString("t\nest data");
first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// Do not copy NoCopySpan if specified so.
final SpannedString copied = new SpannedString(
new CustomSpannable(first), false /* copyNoCopySpan */);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(2, spans.length);
for (int i = 0; i < spans.length; i++) {
assertFalse(spans[i] instanceof NoCopySpan);
}
}
// A custom implementation of Spannable.
private static class CustomSpannable implements Spannable {
private final @NonNull Spannable mText;
CustomSpannable(@NonNull Spannable text) {
mText = text;
}
@Override
public void setSpan(Object what, int start, int end, int flags) {
mText.setSpan(what, start, end, flags);
}
@Override
public void removeSpan(Object what) {
mText.removeSpan(what);
}
@Override
public <T> T[] getSpans(int start, int end, Class<T> type) {
return mText.getSpans(start, end, type);
}
@Override
public int getSpanStart(Object tag) {
return mText.getSpanStart(tag);
}
@Override
public int getSpanEnd(Object tag) {
return mText.getSpanEnd(tag);
}
@Override
public int getSpanFlags(Object tag) {
return mText.getSpanFlags(tag);
}
@Override
public int nextSpanTransition(int start, int limit, Class type) {
return mText.nextSpanTransition(start, limit, type);
}
@Override
public int length() {
return mText.length();
}
@Override
public char charAt(int index) {
return mText.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return mText.subSequence(start, end);
}
@Override
public String toString() {
return mText.toString();
}
};
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.text;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import android.annotation.NonNull;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.style.QuoteSpan;
import android.text.style.UnderlineSpan;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SpannedStringNoCopyTest {
@Test
public void testCopyConstructor_copyNoCopySpans_SpannableStringInternalImpl() {
final SpannableString first = new SpannableString("t\nest data");
first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// By default, copy NoCopySpans
final SpannedString copied = new SpannedString(first);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(3, spans.length);
}
@Test
public void testCopyConstructor_doesNotCopyNoCopySpans_SpannableStringInternalImpl() {
final SpannableString first = new SpannableString("t\nest data");
first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// Do not copy NoCopySpan if specified so.
final SpannedString copied = new SpannedString(first, false /* copyNoCopySpan */);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(2, spans.length);
for (int i = 0; i < spans.length; i++) {
assertFalse(spans[i] instanceof NoCopySpan);
}
}
@Test
public void testCopyConstructor_copyNoCopySpans_OtherSpannedImpl() {
final SpannableString first = new SpannableString("t\nest data");
first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// By default, copy NoCopySpans
final SpannedString copied = new SpannedString(new CustomSpanned(first));
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(3, spans.length);
}
@Test
public void testCopyConstructor_doesNotCopyNoCopySpans_OtherSpannedImpl() {
final SpannableString first = new SpannableString("t\nest data");
first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// Do not copy NoCopySpan if specified so.
final SpannedString copied = new SpannedString(
new CustomSpanned(first), false /* copyNoCopySpan */);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(2, spans.length);
for (int i = 0; i < spans.length; i++) {
assertFalse(spans[i] instanceof NoCopySpan);
}
}
// A custom implementation of Spanned
private static class CustomSpanned implements Spanned {
private final @NonNull Spanned mText;
CustomSpanned(@NonNull Spannable text) {
mText = text;
}
@Override
public <T> T[] getSpans(int start, int end, Class<T> type) {
return mText.getSpans(start, end, type);
}
@Override
public int getSpanStart(Object tag) {
return mText.getSpanStart(tag);
}
@Override
public int getSpanEnd(Object tag) {
return mText.getSpanEnd(tag);
}
@Override
public int getSpanFlags(Object tag) {
return mText.getSpanFlags(tag);
}
@Override
public int nextSpanTransition(int start, int limit, Class type) {
return mText.nextSpanTransition(start, limit, type);
}
@Override
public int length() {
return mText.length();
}
@Override
public char charAt(int index) {
return mText.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return mText.subSequence(start, end);
}
@Override
public String toString() {
return mText.toString();
}
};
}