Merge "Update SORTED_CHAR_MAP"
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 50e5023..86bb255 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -160,6 +160,8 @@
     public static final int CODE_DOUBLE_QUOTE = '"';
     public static final int CODE_QUESTION_MARK = '?';
     public static final int CODE_EXCLAMATION_MARK = '!';
+    public static final int CODE_SLASH = '/';
+    public static final int CODE_COMMERCIAL_AT = '@';
     // TODO: Check how this should work for right-to-left languages. It seems to stand
     // that for rtl languages, a closing parenthesis is a left parenthesis. Is this
     // managed by the font? Or is it a different char?
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 22d1899..75c2cf2 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -252,7 +252,7 @@
     }
 
     private static boolean isValidName(final String name) {
-        if (name != null && -1 == name.indexOf('@')) {
+        if (name != null && -1 == name.indexOf(Constants.CODE_COMMERCIAL_AT)) {
             return true;
         }
         return false;
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index ecb2014..46194f6 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -33,7 +33,6 @@
     private static final int[] SUPPRESSING_AUTO_SPACES_FIELD_VARIATION = {
         InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
         InputType.TYPE_TEXT_VARIATION_PASSWORD,
-        InputType.TYPE_TEXT_VARIATION_URI,
         InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
         InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD };
     public static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0a6f2ab..0e1c4dc 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -2559,7 +2559,8 @@
 
     // This essentially inserts a space, and that's it.
     public void promotePhantomSpace() {
-        if (mSettings.getCurrent().shouldInsertSpacesAutomatically()) {
+        if (mSettings.getCurrent().shouldInsertSpacesAutomatically()
+                && !mConnection.textBeforeCursorLooksLikeURL()) {
             sendKeyCodePoint(Constants.CODE_SPACE);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_promotePhantomSpace();
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index e178466..8ed7ab2 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -19,7 +19,6 @@
 import android.inputmethodservice.InputMethodService;
 import android.text.SpannableString;
 import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
@@ -721,4 +720,15 @@
         // position and the expected position, then it must be a belated update.
         return (newSelStart - oldSelStart) * (mCurrentCursorPosition - newSelStart) >= 0;
     }
+
+    /**
+     * Looks at the text just before the cursor to find out if it looks like a URL.
+     *
+     * The weakest point here is, if we don't have enough text bufferized, we may fail to realize
+     * we are in URL situation, but other places in this class have the same limitation and it
+     * does not matter too much in the practice.
+     */
+    public boolean textBeforeCursorLooksLikeURL() {
+        return StringUtils.lastPartLooksLikeURL(mCommittedTextBeforeComposingText);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 7f1e7c6..d5ee58a 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -282,4 +282,69 @@
         }
         return builder.toString();
     }
+
+    /**
+     * Approximates whether the text before the cursor looks like a URL.
+     *
+     * This is not foolproof, but it should work well in the practice.
+     * Essentially it walks backward from the cursor until it finds something that's not a letter,
+     * digit, or common URL symbol like underscore. If it hasn't found a period yet, then it
+     * does not look like a URL.
+     * If the text:
+     * - starts with www and contains a period
+     * - starts with a slash preceded by either a slash, whitespace, or start-of-string
+     * Then it looks like a URL and we return true. Otherwise, we return false.
+     *
+     * Note: this method is called quite often, and should be fast.
+     *
+     * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the
+     * code complexity, but ideally it should not. It's acceptable for now.
+     */
+    public static boolean lastPartLooksLikeURL(final CharSequence text) {
+        int i = text.length();
+        if (0 == i) return false;
+        int wCount = 0;
+        int slashCount = 0;
+        boolean hasSlash = false;
+        boolean hasPeriod = false;
+        int codePoint = 0;
+        while (i > 0) {
+            codePoint =  Character.codePointBefore(text, i);
+            if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') {
+                // Handwavy heuristic to see if that's a URL character. Anything between period
+                // and z. This includes all lower- and upper-case ascii letters, period,
+                // underscore, arrobase, question mark, equal sign. It excludes spaces, exclamation
+                // marks, double quotes...
+                // Anything that's not a URL-like character causes us to break from here and
+                // evaluate normally.
+                break;
+            }
+            if (Constants.CODE_PERIOD == codePoint) {
+                hasPeriod = true;
+            }
+            if (Constants.CODE_SLASH == codePoint) {
+                hasSlash = true;
+                if (2 == ++slashCount) {
+                    return true;
+                }
+            } else {
+                slashCount = 0;
+            }
+            if ('w' == codePoint) {
+                ++wCount;
+            } else {
+                wCount = 0;
+            }
+            i = Character.offsetByCodePoints(text, i, -1);
+        }
+        // End of the text run.
+        // If it starts with www and includes a period, then it looks like a URL.
+        if (wCount >= 3 && hasPeriod) return true;
+        // If it starts with a slash, and the code point before is whitespace, it looks like an URL.
+        if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true;
+        // If it has both a period and a slash, it looks like an URL.
+        if (hasPeriod && hasSlash) return true;
+        // Otherwise, it doesn't look like an URL.
+        return false;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 96b2c81..da86572 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -189,10 +189,12 @@
         int letterCount = 0;
         for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
             final int codePoint = text.codePointAt(i);
-            // Any word containing a '@' is probably an e-mail address
-            // Any word containing a '/' is probably either an ad-hoc combination of two
+            // Any word containing a COMMERCIAL_AT is probably an e-mail address
+            // Any word containing a SLASH is probably either an ad-hoc combination of two
             // words or a URI - in either case we don't want to spell check that
-            if ('@' == codePoint || '/' == codePoint) return true;
+            if (Constants.CODE_COMMERCIAL_AT == codePoint || Constants.CODE_SLASH == codePoint) {
+                return true;
+            }
             if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
         }
         // Guestimate heuristic: perform spell checking if at least 3/4 of the characters
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
index dc8837d..aacd60f 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
@@ -85,6 +85,11 @@
         public boolean endBatchEdit() {
             return true;
         }
+
+        @Override
+        public boolean finishComposingText() {
+            return true;
+        }
     }
 
     private class MockInputMethodService extends InputMethodService {
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index 98a50b7..1e3cc8a 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -215,4 +215,26 @@
         checkCapitalize("Lorem!Ipsum (dolor) Sit * Amet", "Lorem!Ipsum (Dolor) Sit * Amet",
                 " \n,.;!?*()&", Locale.ENGLISH);
     }
+
+    public void testLooksLikeURL() {
+        assertTrue(StringUtils.lastPartLooksLikeURL("http://www.google."));
+        assertFalse(StringUtils.lastPartLooksLikeURL("word wo"));
+        assertTrue(StringUtils.lastPartLooksLikeURL("/etc/foo"));
+        assertFalse(StringUtils.lastPartLooksLikeURL("left/right"));
+        assertTrue(StringUtils.lastPartLooksLikeURL("www.goo"));
+        assertTrue(StringUtils.lastPartLooksLikeURL("www."));
+        assertFalse(StringUtils.lastPartLooksLikeURL("U.S.A"));
+        assertFalse(StringUtils.lastPartLooksLikeURL("U.S.A."));
+        assertTrue(StringUtils.lastPartLooksLikeURL("rtsp://foo."));
+        assertTrue(StringUtils.lastPartLooksLikeURL("://"));
+        assertFalse(StringUtils.lastPartLooksLikeURL("abc/"));
+        assertTrue(StringUtils.lastPartLooksLikeURL("abc.def/ghi"));
+        assertFalse(StringUtils.lastPartLooksLikeURL("abc.def"));
+        // TODO: ideally this would not look like a URL, but to keep down the complexity of the
+        // code for now True is acceptable.
+        assertTrue(StringUtils.lastPartLooksLikeURL("abc./def"));
+        // TODO: ideally this would not look like a URL, but to keep down the complexity of the
+        // code for now True is acceptable.
+        assertTrue(StringUtils.lastPartLooksLikeURL(".abc/def"));
+    }
 }