Introduce new constructor for not copying NoCopySpan

To hold the original text in PrecomputedText, need to create
SpannableString, but copying NoCopySpan causes some side effect.
This CL introduces a way of copying SpannableString/SpannedString
with all spans other than NoCopySpan.

Bug: 72998298
Bug: 35638900
Test: atest CtsWidgetTestCases:EditTextTest
    CtsWidgetTestCases:TextViewFadingEdgeTest
    FrameworksCoreTests:TextViewFallbackLineSpacingTest
    FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest
    CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest
    CtsTextTestCases FrameworksCoreTests:android.text
    CtsWidgetTestCases:TextViewPrecomputedTextTest

Change-Id: I20dea2114ccaa54b16ff679c97682a5003f9a4c1
diff --git a/core/java/android/text/SpannableString.java b/core/java/android/text/SpannableString.java
index 56d0946..afb5df8 100644
--- a/core/java/android/text/SpannableString.java
+++ b/core/java/android/text/SpannableString.java
@@ -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 @@
 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) {
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
index 366ec14..5dd1a52 100644
--- a/core/java/android/text/SpannableStringInternal.java
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -26,7 +26,7 @@
 /* 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 @@
 
         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 @@
      * @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 @@
         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;
diff --git a/core/java/android/text/SpannedString.java b/core/java/android/text/SpannedString.java
index afed221..acee3c5 100644
--- a/core/java/android/text/SpannedString.java
+++ b/core/java/android/text/SpannedString.java
@@ -26,12 +26,27 @@
 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) {
diff --git a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
new file mode 100644
index 0000000..c205f96
--- /dev/null
+++ b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
@@ -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();
+        }
+    };
+}
diff --git a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
new file mode 100644
index 0000000..0680924
--- /dev/null
+++ b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
@@ -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();
+        }
+    };
+}