Merge "Remove experimental settings"
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index acc6e58..2f591fd 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -82,6 +82,8 @@
 
     <!-- Option to configure dictionaries -->
     <string name="configure_dictionaries_title">Add-on dictionaries</string>
+    <!-- Name of the main dictionary, as opposed to auxiliary dictionaries (medical/entertainment/sports...) -->
+    <string name="main_dictionary">Main dictionary</string>
 
     <!-- Option to enable showing suggestions -->
     <string name="prefs_show_suggestions">Show correction suggestions</string>
diff --git a/java/res/xml/kbd_rows_symbols.xml b/java/res/xml/kbd_rows_symbols.xml
index e0ede8b..2235f4d 100644
--- a/java/res/xml/kbd_rows_symbols.xml
+++ b/java/res/xml/kbd_rows_symbols.xml
@@ -101,8 +101,8 @@
         <Key
             latin:keyLabel="!"
             latin:moreKeys="¡" />
-        <!-- Note: DroidSans doesn't have double-high-reversed-quotation '\u201f' glyph. -->
-        <!-- latin:moreKeys="“,”,„,‟,«,»" -->
+        <!-- Note: Neither DroidSans nor Roboto have a glyph for ‟ Double high-reversed-9 quotation mark U+201F. -->
+            <!-- latin:moreKeys="“,”,„,‟,«,»" -->
         <Key
             latin:keyLabel="&quot;"
             latin:moreKeys="“,”,«,»"
diff --git a/java/res/xml/kbd_symbols_shift_row4.xml b/java/res/xml/kbd_symbols_shift_row4.xml
index c8d5293..99fa80a 100644
--- a/java/res/xml/kbd_symbols_shift_row4.xml
+++ b/java/res/xml/kbd_symbols_shift_row4.xml
@@ -32,9 +32,11 @@
                 <Key
                     latin:keyStyle="toAlphaKeyStyle"
                     latin:keyWidth="15%p" />
+                <!-- Note: Neither DroidSans nor Roboto have a glyph for ‟ Double high-reversed-9 quotation mark U+201F. -->
+                    <!-- latin:keyLabelOption="hasPopupHint" -->
+                    <!-- latin:moreKeys="‟" -->
                 <Key
                     latin:keyLabel="„"
-                    latin:moreKeys="“,”,„,‟,«,»,‘,’,‚,‛"
                     latin:keyStyle="functionalKeyStyle" />
                 <Key
                     latin:keyStyle="spaceKeyStyle"
@@ -53,9 +55,11 @@
                     latin:keyWidth="13.75%p" />
                 <include
                     latin:keyboardLayout="@xml/kbd_settings_or_tab" />
+                <!-- Note: Neither DroidSans nor Roboto have a glyph for ‟ Double high-reversed-9 quotation mark U+201F. -->
+                    <!-- latin:keyLabelOption="hasPopupHint" -->
+                    <!-- latin:moreKeys="‟" -->
                 <Key
                     latin:keyLabel="„"
-                    latin:moreKeys="“,”,„,‟,«,»,‘,’,‚,‛"
                     latin:keyWidth="9.2%p"
                     latin:keyStyle="functionalKeyStyle" />
                 <Key
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 6608d82..d696a61 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -25,16 +25,17 @@
 import android.net.Uri;
 import android.os.RemoteException;
 import android.provider.UserDictionary.Words;
+import android.text.TextUtils;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 
 public class UserDictionary extends ExpandableDictionary {
-    
+
     private static final String[] PROJECTION_QUERY = {
         Words.WORD,
         Words.FREQUENCY,
     };
-    
+
     private static final String[] PROJECTION_ADD = {
         Words._ID,
         Words.FREQUENCY,
@@ -42,10 +43,11 @@
     };
 
     private ContentObserver mObserver;
-    private String mLocale;
+    final private String mLocale;
 
     public UserDictionary(Context context, String locale) {
         super(context, Suggest.DIC_USER);
+        if (null == locale) throw new NullPointerException(); // Catch the error earlier
         mLocale = locale;
         // Perform a managed query. The Activity will handle closing and re-querying the cursor
         // when needed.
@@ -73,9 +75,35 @@
 
     @Override
     public void loadDictionaryAsync() {
+        // Split the locale. For example "en" => ["en"], "de_DE" => ["de", "DE"],
+        // "en_US_foo_bar_qux" => ["en", "US", "foo_bar_qux"] because of the limit of 3.
+        // This is correct for locale processing.
+        // For this example, we'll look at the "en_US_POSIX" case.
+        final String[] localeElements =
+                TextUtils.isEmpty(mLocale) ? new String[] {} : mLocale.split("_", 3);
+
+        final StringBuilder request = new StringBuilder("(locale is NULL)");
+        String localeSoFar = "";
+        // At start, localeElements = ["en", "US", "POSIX"] ; localeSoFar = "" ;
+        // and request = "(locale is NULL)"
+        for (int i = 0; i < localeElements.length; ++i) {
+            // i | localeSoFar    | localeElements
+            // 0 | ""             | ["en", "US", "POSIX"]
+            // 1 | "en_"          | ["en", "US", "POSIX"]
+            // 2 | "en_US_"       | ["en", "en_US", "POSIX"]
+            localeElements[i] = localeSoFar + localeElements[i];
+            localeSoFar = localeElements[i] + "_";
+            // i | request
+            // 0 | "(locale is NULL)"
+            // 1 | "(locale is NULL) or (locale=?)"
+            // 2 | "(locale is NULL) or (locale=?) or (locale=?)"
+            request.append(" or (locale=?)");
+        }
+        // At the end, localeElements = ["en", "en_US", "en_US_POSIX"]; localeSoFar = en_US_POSIX_"
+        // and request = "(locale is NULL) or (locale=?) or (locale=?) or (locale=?)"
         Cursor cursor = getContext().getContentResolver()
-                .query(Words.CONTENT_URI, PROJECTION_QUERY, "(locale IS NULL) or (locale=?)",
-                        new String[] { mLocale }, null);
+                .query(Words.CONTENT_URI, PROJECTION_QUERY, request.toString(),
+                        localeElements, null);
         addWords(cursor);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 3cf8788..dfa0abf 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -282,6 +282,42 @@
             mLocale = LocaleUtils.constructLocaleFromString(localeString);
         }
 
+        /**
+         * Finds out whether a particular string should be filtered out of spell checking.
+         *
+         * This will loosely match URLs, numbers, symbols.
+         *
+         * @param text the string to evaluate.
+         * @return true if we should filter this text out, false otherwise
+         */
+        private boolean shouldFilterOut(final String text) {
+            if (TextUtils.isEmpty(text) || text.length() <= 1) return true;
+
+            // TODO: check if an equivalent processing can't be done more quickly with a
+            // compiled regexp.
+            // Filter by first letter
+            final int firstCodePoint = text.codePointAt(0);
+            // Filter out words that don't start with a letter or an apostrophe
+            if (!Character.isLetter(firstCodePoint)
+                    && '\'' != firstCodePoint) return true;
+
+            // Filter contents
+            final int length = text.length();
+            int letterCount = 0;
+            for (int i = 0; i < length; ++i) {
+                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
+                // words or a URI - in either case we don't want to spell check that
+                if ('@' == codePoint
+                        || '/' == codePoint) return true;
+                if (Character.isLetter(codePoint)) ++letterCount;
+            }
+            // Guestimate heuristic: perform spell checking if at least 3/4 of the characters
+            // in this word are letters
+            return (letterCount * 4 < length * 3);
+        }
+
         // Note : this must be reentrant
         /**
          * Gets a list of suggestions for a specific string. This returns a list of possible
@@ -293,7 +329,7 @@
                 final int suggestionsLimit) {
             final String text = textInfo.getText();
 
-            if (TextUtils.isEmpty(text)) return EMPTY_SUGGESTIONS_INFO;
+            if (shouldFilterOut(text)) return EMPTY_SUGGESTIONS_INFO;
 
             final SuggestionsGatherer suggestionsGatherer =
                     new SuggestionsGatherer(suggestionsLimit);
diff --git a/native/src/correction.cpp b/native/src/correction.cpp
index 5a0e608..d5bfed0 100644
--- a/native/src/correction.cpp
+++ b/native/src/correction.cpp
@@ -429,6 +429,9 @@
         if (multiplier == 2) {
             *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX;
         } else {
+            // TODO: This overflow check gives a wrong answer when, for example,
+            //       temp = 2^16 + 1 and multiplier = 2^17 + 1.
+            //       Fix this behavior.
             const int tempRetval = temp * multiplier;
             *base = tempRetval >= temp ? tempRetval : S_INT_MAX;
         }
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index 4e671a1..517dc84 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -348,20 +348,6 @@
     }
 }
 
-static const int TWO_31ST_DIV_2 = S_INT_MAX / 2;
-inline static void multiplyIntCapped(const int multiplier, int *base) {
-    const int temp = *base;
-    if (temp != S_INT_MAX) {
-        // Branch if multiplier == 2 for the optimization
-        if (multiplier == 2) {
-            *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX;
-        } else {
-            const int tempRetval = temp * multiplier;
-            *base = tempRetval >= temp ? tempRetval : S_INT_MAX;
-        }
-    }
-}
-
 void UnigramDictionary::getMissingSpaceWords(
         const int inputLength, const int missingSpacePos, Correction *correction) {
     correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,