Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 54ff524a authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents 61de050b 3483bc7d
Loading
Loading
Loading
Loading
+17 −3
Original line number Diff line number Diff line
@@ -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) {
+61 −20
Original line number Diff line number Diff line
@@ -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()) {
            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 {
    private void copySpans(SpannableStringInternal src, int start, int end,
            boolean ignoreNoCopySpan) {
        int count = 0;
            int[] srcData = src.mSpanData;
            int limit = src.mSpanCount;
        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;

            Object[] srcSpans = src.mSpans;
        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 {
            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;
+17 −2
Original line number Diff line number Diff line
@@ -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) {
+163 −0
Original line number Diff line number Diff line
/*
 * 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();
        }
    };
}
+153 −0
Original line number Diff line number Diff line
/*
 * 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();
        }
    };
}