OSDN Git Service

Fix SpannableStringBuilder.replace() exception.
authorSiyamed Sinir <siyamed@google.com>
Wed, 6 Jan 2016 21:02:59 +0000 (13:02 -0800)
committerSiyamed Sinir <siyamed@google.com>
Thu, 7 Jan 2016 20:56:09 +0000 (12:56 -0800)
When SpannableStringBuilder.replace is called with a source text that
contains a span with SPAN_PARAGRAPH flag, the code tries to retain the
spans. However when the paragraph boundary constraint is not satisfied,
setSpan method throws an exception. Updated the rule as: if the source
text has a span with SPAN_PARAGRAPH flag check if it can be copied into
the target. If not, discard the span. Also updated the JavaDoc for
Spanned and Editable.

Bug: 22521443
Change-Id: Ie8541e00bfdf5b8b0115ad7b26cb9f83a6a3ee55

core/java/android/text/Editable.java
core/java/android/text/SpannableStringBuilder.java
core/java/android/text/Spanned.java

index a284a00..b3f2c2a 100644 (file)
@@ -40,10 +40,14 @@ extends CharSequence, GetChars, Spannable, Appendable
      * is Spanned, the spans from it are preserved into the Editable.
      * Existing spans within the Editable that entirely cover the replaced
      * range are retained, but any that were strictly within the range
-     * that was replaced are removed.  As a special case, the cursor
-     * position is preserved even when the entire range where it is
-     * located is replaced.
+     * that was replaced are removed. If the <code>source</code> contains a span
+     * with {@link Spanned#SPAN_PARAGRAPH} flag, and it does not satisfy the
+     * paragraph boundary constraint, it is not retained. As a special case, the
+     * cursor position is preserved even when the entire range where it is located
+     * is replaced.
      * @return  a reference to this object.
+     *
+     * @see Spanned#SPAN_PARAGRAPH
      */
     public Editable replace(int st, int en, CharSequence source, int start, int end);
 
index 40315ad..4267238 100644 (file)
@@ -421,8 +421,17 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
 
                 // Add span only if this object is not yet used as a span in this string
                 if (getSpanStart(spans[i]) < 0) {
-                    setSpan(false, spans[i], st - csStart + start, en - csStart + start,
-                            sp.getSpanFlags(spans[i]) | SPAN_ADDED);
+                    int copySpanStart = st - csStart + start;
+                    int copySpanEnd = en - csStart + start;
+                    int copySpanFlags = sp.getSpanFlags(spans[i]) | SPAN_ADDED;
+
+                    int flagsStart = (copySpanFlags & START_MASK) >> START_SHIFT;
+                    int flagsEnd = copySpanFlags & END_MASK;
+
+                    if(!isInvalidParagraphStart(copySpanStart, flagsStart) &&
+                            !isInvalidParagraphEnd(copySpanEnd, flagsEnd)) {
+                        setSpan(false, spans[i], copySpanStart, copySpanEnd, copySpanFlags);
+                    }
                 }
             }
             restoreInvariants();
@@ -666,23 +675,13 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
         checkRange("setSpan", start, end);
 
         int flagsStart = (flags & START_MASK) >> START_SHIFT;
-        if (flagsStart == PARAGRAPH) {
-            if (start != 0 && start != length()) {
-                char c = charAt(start - 1);
-
-                if (c != '\n')
-                    throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
-            }
+        if(isInvalidParagraphStart(start, flagsStart)) {
+            throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
         }
 
         int flagsEnd = flags & END_MASK;
-        if (flagsEnd == PARAGRAPH) {
-            if (end != 0 && end != length()) {
-                char c = charAt(end - 1);
-
-                if (c != '\n')
-                    throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
-            }
+        if(isInvalidParagraphEnd(end, flagsEnd)) {
+            throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
         }
 
         // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
@@ -761,6 +760,28 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
         }
     }
 
+    private final boolean isInvalidParagraphStart(int start, int flagsStart) {
+        if (flagsStart == PARAGRAPH) {
+            if (start != 0 && start != length()) {
+                char c = charAt(start - 1);
+
+                if (c != '\n') return true;
+            }
+        }
+        return false;
+    }
+
+    private final boolean isInvalidParagraphEnd(int end, int flagsEnd) {
+        if (flagsEnd == PARAGRAPH) {
+            if (end != 0 && end != length()) {
+                char c = charAt(end - 1);
+
+                if (c != '\n') return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Remove the specified markup object from the buffer.
      */
index a785d1b..a0d54c2 100644 (file)
@@ -81,7 +81,9 @@ extends CharSequence
      * immediately after a \n character, and if the \n
      * that anchors it is deleted, the endpoint is pulled to the
      * next \n that follows in the buffer (or to the end of
-     * the buffer).
+     * the buffer). If a span with SPAN_PARAGRAPH flag is pasted
+     * into another text and the paragraph boundary constraint
+     * is not satisfied, the span is discarded.
      */
     public static final int SPAN_PARAGRAPH =   0x33;