Fix case sensitivity for the spell checker.

The new behavior is as follows:
- If the word in the dictionary is not fully lower case, then the
exact case is required to match.
- If the word in the dictionary is fully lower case, then any of
the following patterns match:
  - fully lower case
  - only the first char capitalized
  - all caps
Any other capitalization is rejected.

This is probably what people want. If you type a name in all lower
case, it should be marked as a typo, but if you type a word with a
capital for emphasis or just because it's the start of the sentence,
it should match a lower case word in the dictionary. If you have
a spurious capital letter in the middle of a word because of a typo,
it should be marked as such.

Accents are not affected, and should not be. An accented letter
is a different letter and a missing accent should be reported.
We should maybe consider again for some common transpositions
like the "ue" digraph for German, which is now considered a typo,
but will suggest the correct diacritics as the first suggestion.

Bug: 5145751
Change-Id: I651e24f13c90fb94700a1674ad380e95336e7dca
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index ec82f9e..2117624 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -23,6 +23,7 @@
 import android.util.Log;
 import android.view.textservice.SuggestionsInfo;
 import android.view.textservice.TextInfo;
+import android.text.TextUtils;
 
 import com.android.inputmethod.compat.ArraysCompatUtils;
 import com.android.inputmethod.keyboard.Key;
@@ -50,7 +51,8 @@
     private static final boolean DBG = false;
     private static final int POOL_SIZE = 2;
 
-    private final static String[] emptyArray = new String[0];
+    private final static SuggestionsInfo EMPTY_SUGGESTIONS_INFO =
+            new SuggestionsInfo(0, new String[0]);
     private Map<String, DictionaryPool> mDictionaryPools =
             Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
     private Map<String, Dictionary> mUserDictionaries =
@@ -153,10 +155,14 @@
     private class AndroidSpellCheckerSession extends Session {
         // Immutable, but need the locale which is not available in the constructor yet
         DictionaryPool mDictionaryPool;
+        // Likewise
+        Locale mLocale;
 
         @Override
         public void onCreate() {
-            mDictionaryPool = getDictionaryPool(getLocale());
+            final String localeString = getLocale();
+            mDictionaryPool = getDictionaryPool(localeString);
+            mLocale = Utils.constructLocaleFromString(localeString);
         }
 
         // Note : this must be reentrant
@@ -170,6 +176,8 @@
                 final int suggestionsLimit) {
             final String text = textInfo.getText();
 
+            if (TextUtils.isEmpty(text)) return EMPTY_SUGGESTIONS_INFO;
+
             final SuggestionsGatherer suggestionsGatherer =
                     new SuggestionsGatherer(suggestionsLimit);
             final WordComposer composer = new WordComposer();
@@ -194,12 +202,32 @@
                 dictInfo.mDictionary.getWords(composer, suggestionsGatherer,
                         dictInfo.mProximityInfo);
                 isInDict = dictInfo.mDictionary.isValidWord(text);
+                if (!isInDict && Character.isUpperCase(text.codePointAt(0))) {
+                    // If the first char is not uppercase, then the word is either all lower case,
+                    // in which case we already tested it, or mixed case, in which case we don't
+                    // want to test a lower-case version of it. Hence the test above.
+                    // Also note that by isEmpty() test at the top of the method codePointAt(0) is
+                    // guaranteed to be there.
+                    final int len = text.codePointCount(0, text.length());
+                    int capsCount = 1;
+                    for (int i = 1; i < len; ++i) {
+                        if (1 != capsCount && i != capsCount) break;
+                        if (Character.isUpperCase(text.codePointAt(i))) ++capsCount;
+                    }
+                    // We know the first char is upper case. So we want to test if either everything
+                    // else is lower case, or if everything else is upper case. If the string is
+                    // exactly one char long, then we will arrive here with capsCount 0, and this is
+                    // correct, too.
+                    if (1 == capsCount || len == capsCount) {
+                        isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
+                    }
+                }
                 if (!mDictionaryPool.offer(dictInfo)) {
                     Log.e(TAG, "Can't re-insert a dictionary into its pool");
                 }
             } catch (InterruptedException e) {
                 // I don't think this can happen.
-                return new SuggestionsInfo(0, new String[0]);
+                return EMPTY_SUGGESTIONS_INFO;
             }
 
             final String[] suggestions = suggestionsGatherer.getGatheredSuggestions();