From 7a7aeffcdcf1f25abdc3923e81fa19c2258fa9e9 Mon Sep 17 00:00:00 2001
From: Jean Chalard <jchalard@google.com>
Date: Mon, 17 Feb 2014 17:53:15 +0900
Subject: [PATCH] Don't resume suggestion on links.

Bug: 11447084
Change-Id: I5bd558b9dd85d1505aa918f44e8ac3e52ec42d97
---
 .../inputmethod/latin/RichInputConnection.java      |  6 +++++-
 .../inputmethod/latin/inputlogic/InputLogic.java    |  4 ++++
 .../latin/utils/SpannableStringUtils.java           | 13 +++++++++++++
 .../android/inputmethod/latin/utils/TextRange.java  |  6 ++++--
 4 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 0d0b7a160b..eb1899ca2d 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -668,12 +668,16 @@ public final class RichInputConnection {
             }
         }
 
+        final boolean hasUrlSpans =
+                SpannableStringUtils.hasUrlSpans(before, startIndexInBefore, before.length())
+                || SpannableStringUtils.hasUrlSpans(after, 0, endIndexInAfter);
         // We don't use TextUtils#concat because it copies all spans without respect to their
         // nature. If the text includes a PARAGRAPH span and it has been split, then
         // TextUtils#concat will crash when it tries to concat both sides of it.
         return new TextRange(
                 SpannableStringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after),
-                        startIndexInBefore, before.length() + endIndexInAfter, before.length());
+                        startIndexInBefore, before.length() + endIndexInAfter, before.length(),
+                        hasUrlSpans);
     }
 
     public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 6f73859e8a..9bf9d1f452 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -1270,6 +1270,10 @@ public final class InputLogic {
         if (range.length() <= 0) return; // Race condition. No text to resume on, so bail out.
         // If for some strange reason (editor bug or so) we measure the text before the cursor as
         // longer than what the entire text is supposed to be, the safe thing to do is bail out.
+        if (range.mHasUrlSpans) return; // If there are links, we don't resume suggestions. Making
+        // edits to a linkified text through batch commands would ruin the URL spans, and unless
+        // we take very complicated steps to preserve the whole link, we can't do things right so
+        // we just do not resume because it's safer.
         final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
         if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return;
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
index be0955456c..38164cb364 100644
--- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -22,6 +22,7 @@ import android.text.Spanned;
 import android.text.SpannedString;
 import android.text.TextUtils;
 import android.text.style.SuggestionSpan;
+import android.text.style.URLSpan;
 
 public final class SpannableStringUtils {
     /**
@@ -112,4 +113,16 @@ public final class SpannableStringUtils {
 
         return new SpannedString(ss);
     }
+
+    public static boolean hasUrlSpans(final CharSequence text,
+            final int startIndex, final int endIndex) {
+        if (!(text instanceof Spanned)) {
+            return false; // Not spanned, so no link
+        }
+        final Spanned spanned = (Spanned)text;
+        // getSpans(x, y) does not return spans that start on x or end on y. x-1, y+1 does the
+        // trick, and works in all cases even if startIndex <= 0 or endIndex >= text.length().
+        final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class);
+        return null != spans && spans.length > 0;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/TextRange.java b/java/src/com/android/inputmethod/latin/utils/TextRange.java
index 48b443ddd9..dbf3b5060c 100644
--- a/java/src/com/android/inputmethod/latin/utils/TextRange.java
+++ b/java/src/com/android/inputmethod/latin/utils/TextRange.java
@@ -31,6 +31,7 @@ public final class TextRange {
     private final int mCursorIndex;
 
     public final CharSequence mWord;
+    public final boolean mHasUrlSpans;
 
     public int getNumberOfCharsInWordBeforeCursor() {
         return mCursorIndex - mWordAtCursorStartIndex;
@@ -95,7 +96,7 @@ public final class TextRange {
                 }
             }
             if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) {
-                // If the span does not start and stop here, we ignore it. It probably extends
+                // If the span does not start and stop here, ignore it. It probably extends
                 // past the start or end of the word, as happens in missing space correction
                 // or EasyEditSpans put by voice input.
                 spans[writeIndex++] = spans[readIndex];
@@ -105,7 +106,7 @@ public final class TextRange {
     }
 
     public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
-            final int wordAtCursorEndIndex, final int cursorIndex) {
+            final int wordAtCursorEndIndex, final int cursorIndex, final boolean hasUrlSpans) {
         if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
                 || cursorIndex > wordAtCursorEndIndex
                 || wordAtCursorEndIndex > textAtCursor.length()) {
@@ -115,6 +116,7 @@ public final class TextRange {
         mWordAtCursorStartIndex = wordAtCursorStartIndex;
         mWordAtCursorEndIndex = wordAtCursorEndIndex;
         mCursorIndex = cursorIndex;
+        mHasUrlSpans = hasUrlSpans;
         mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
     }
 }
\ No newline at end of file
-- 
GitLab