Merge "Fix crash when modifying Selection" into pi-dev
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 3445658..5256e47 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -180,7 +180,7 @@
      * Remove the selection or cursor, if any, from the text.
      */
     public static final void removeSelection(Spannable text) {
-        text.removeSpan(SELECTION_START);
+        text.removeSpan(SELECTION_START, Spanned.SPAN_INTERMEDIATE);
         text.removeSpan(SELECTION_END);
         removeMemory(text);
     }
diff --git a/core/java/android/text/Spannable.java b/core/java/android/text/Spannable.java
index 39b78eb..8315b2a 100644
--- a/core/java/android/text/Spannable.java
+++ b/core/java/android/text/Spannable.java
@@ -46,6 +46,19 @@
     public void removeSpan(Object what);
 
     /**
+     * Remove the specified object from the range of text to which it
+     * was attached, if any.  It is OK to remove an object that was never
+     * attached in the first place.
+     *
+     * See {@link Spanned} for an explanation of what the flags mean.
+     *
+     * @hide
+     */
+    default void removeSpan(Object what, int flags) {
+        removeSpan(what);
+    }
+
+    /**
      * Factory used by TextView to create new {@link Spannable Spannables}. You can subclass
      * it to provide something other than {@link SpannableString}.
      *
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index d41dfdc..41a9c45 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -312,7 +312,7 @@
                     // The following condition indicates that the span would become empty
                     (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
                 mIndexOfSpan.remove(mSpans[i]);
-                removeSpan(i);
+                removeSpan(i, 0 /* flags */);
                 return true;
             }
             return resolveGap(mSpanStarts[i]) <= end && (i & 1) != 0 &&
@@ -472,7 +472,7 @@
     }
 
     // Note: caller is responsible for removing the mIndexOfSpan entry.
-    private void removeSpan(int i) {
+    private void removeSpan(int i, int flags) {
         Object object = mSpans[i];
 
         int start = mSpanStarts[i];
@@ -496,7 +496,9 @@
         // Invariants must be restored before sending span removed notifications.
         restoreInvariants();
 
-        sendSpanRemoved(object, start, end);
+        if ((flags & Spanned.SPAN_INTERMEDIATE) == 0) {
+            sendSpanRemoved(object, start, end);
+        }
     }
 
     // Documentation from interface
@@ -782,10 +784,19 @@
      * Remove the specified markup object from the buffer.
      */
     public void removeSpan(Object what) {
+        removeSpan(what, 0 /* flags */);
+    }
+
+    /**
+     * Remove the specified markup object from the buffer.
+     *
+     * @hide
+     */
+    public void removeSpan(Object what, int flags) {
         if (mIndexOfSpan == null) return;
         Integer i = mIndexOfSpan.remove(what);
         if (i != null) {
-            removeSpan(i.intValue());
+            removeSpan(i.intValue(), flags);
         }
     }
 
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
index 5dd1a52..bcc2fda 100644
--- a/core/java/android/text/SpannableStringInternal.java
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -249,6 +249,13 @@
     }
 
     /* package */ void removeSpan(Object what) {
+        removeSpan(what, 0 /* flags */);
+    }
+
+    /**
+     * @hide
+     */
+    public void removeSpan(Object what, int flags) {
         int count = mSpanCount;
         Object[] spans = mSpans;
         int[] data = mSpanData;
@@ -262,11 +269,13 @@
 
                 System.arraycopy(spans, i + 1, spans, i, c);
                 System.arraycopy(data, (i + 1) * COLUMNS,
-                                 data, i * COLUMNS, c * COLUMNS);
+                        data, i * COLUMNS, c * COLUMNS);
 
                 mSpanCount--;
 
-                sendSpanRemoved(what, ostart, oend);
+                if ((flags & Spanned.SPAN_INTERMEDIATE) == 0) {
+                    sendSpanRemoved(what, ostart, oend);
+                }
                 return;
             }
         }
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 5f7a0f7..090e19f 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -522,7 +522,7 @@
             b = tmp;
         }
 
-        if (a == b) return null;
+        if (a == b || a < 0) return null;
 
         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
             return content.subSequence(a, b);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 9946726..6af678b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -6031,7 +6031,9 @@
             mSwitchedLines = false;
             final int selectionStart = mTextView.getSelectionStart();
             final int selectionEnd = mTextView.getSelectionEnd();
-            if (selectionStart > selectionEnd) {
+            if (selectionStart < 0 || selectionEnd < 0) {
+                Selection.removeSelection((Spannable) mTextView.getText());
+            } else if (selectionStart > selectionEnd) {
                 Selection.setSelection((Spannable) mTextView.getText(),
                         selectionEnd, selectionStart);
             }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 11db6b6..fae6db5d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9380,7 +9380,7 @@
         final int selectionStart = getSelectionStart();
         final int selectionEnd = getSelectionEnd();
 
-        return selectionStart >= 0 && selectionStart != selectionEnd;
+        return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
     }
 
     String getSelectedText() {
diff --git a/core/tests/coretests/src/android/text/SpannableTest.java b/core/tests/coretests/src/android/text/SpannableTest.java
index 307ac62..f368428 100644
--- a/core/tests/coretests/src/android/text/SpannableTest.java
+++ b/core/tests/coretests/src/android/text/SpannableTest.java
@@ -16,6 +16,8 @@
 
 package android.text;
 
+import static org.junit.Assert.assertEquals;
+
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -24,6 +26,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -53,4 +57,34 @@
         spans = spannable.getSpans(2, 2, Object.class);
         MoreAsserts.assertEquals(new Object[]{unemptySpan}, spans);
     }
+
+    @Test
+    public void testRemoveSpanWithIntermediateFlag() {
+        Spannable spannable = newSpannableWithText("abcdef");
+        Object emptySpan = new Object();
+        spannable.setSpan(emptySpan, 1, 1, 0);
+        Object unemptySpan = new Object();
+        spannable.setSpan(unemptySpan, 1, 2, 0);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        SpanWatcher watcher = new SpanWatcher() {
+            @Override
+            public void onSpanAdded(Spannable text, Object what, int start, int end) {}
+
+            @Override
+            public void onSpanRemoved(Spannable text, Object what, int start, int end) {
+                latch.countDown();
+            }
+
+            @Override
+            public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart,
+                    int nend) {}
+        };
+        spannable.setSpan(watcher, 0, 2, 0);
+
+        spannable.removeSpan(emptySpan, Spanned.SPAN_INTERMEDIATE);
+        assertEquals(1, latch.getCount());
+        spannable.removeSpan(unemptySpan);
+        assertEquals(0, latch.getCount());
+    }
 }