Fix wrong misspelling reports of fully capitalized words

Two flavors of words would be wrongly reported as misspelled
by the android spell checker when they are written in all
upper case letters:
- Words containing a quote or a dash or any other non-letter
- Words that need the first letter to be capitalized

Bug: 7659216
Change-Id: Ibc5d261945ffcbb8a858d4c73b7c62cef6671abf
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 49b9886..2f146f8 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -438,15 +438,23 @@
         if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE;
         final int len = text.length();
         int capsCount = 1;
+        int letterCount = 1;
         for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) {
-            if (1 != capsCount && i != capsCount) break;
-            if (Character.isUpperCase(text.codePointAt(i))) ++capsCount;
+            if (1 != capsCount && letterCount != capsCount) break;
+            final int codePoint = text.codePointAt(i);
+            if (Character.isUpperCase(codePoint)) {
+                ++capsCount;
+                ++letterCount;
+            } else if (Character.isLetter(codePoint)) {
+                // We need to discount non-letters since they may not be upper-case, but may
+                // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME")
+                ++letterCount;
+            }
         }
-        // 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 1, and this is
-        // correct, too.
+        // We know the first char is upper case. So we want to test if either every letter other
+        // than the first is lower case, or if they are all upper case. If the string is exactly
+        // one char long, then we will arrive here with letterCount 1, and this is correct, too.
         if (1 == capsCount) return CAPITALIZE_FIRST;
-        return (len == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
+        return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index a8f3239..470943b 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -28,9 +28,11 @@
 
 import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
 
 import java.util.ArrayList;
@@ -188,6 +190,35 @@
         return (letterCount * 4 < length * 3);
     }
 
+    /**
+     * Helper method to test valid capitalizations of a word.
+     *
+     * If the "text" is lower-case, we test only the exact string.
+     * If the "Text" is capitalized, we test the exact string "Text" and the lower-cased
+     *  version of it "text".
+     * If the "TEXT" is fully upper case, we test the exact string "TEXT", the lower-cased
+     *  version of it "text" and the capitalized version of it "Text".
+     */
+    private boolean isInDictForAnyCapitalization(final Dictionary dict, final String text,
+            final int capitalizeType) {
+        // If the word is in there as is, then it's in the dictionary. If not, we'll test lower
+        // case versions, but only if the word is not already all-lower case or mixed case.
+        if (dict.isValidWord(text)) return true;
+        if (AndroidSpellCheckerService.CAPITALIZE_NONE == capitalizeType) return false;
+
+        // If we come here, we have a capitalized word (either First- or All-).
+        // Downcase the word and look it up again. If the word is only capitalized, we
+        // tested all possibilities, so if it's still negative we can return false.
+        final String lowerCaseText = text.toLowerCase(mLocale);
+        if (dict.isValidWord(lowerCaseText)) return true;
+        if (AndroidSpellCheckerService.CAPITALIZE_FIRST == capitalizeType) return false;
+
+        // If the lower case version is not in the dictionary, it's still possible
+        // that we have an all-caps version of a word that needs to be capitalized
+        // according to the dictionary. E.g. "GERMANS" only exists in the dictionary as "Germans".
+        return dict.isValidWord(StringUtils.toTitleCase(lowerCaseText, mLocale));
+    }
+
     // Note : this must be reentrant
     /**
      * Gets a list of suggestions for a specific string. This returns a list of possible
@@ -272,13 +303,7 @@
                     suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
                             suggestionStr.length(), suggestion.mScore);
                 }
-                isInDict = dictInfo.mDictionary.isValidWord(text);
-                if (!isInDict && AndroidSpellCheckerService.CAPITALIZE_NONE != capitalizeType) {
-                    // We want to test the word again if it's all caps or first caps only.
-                    // If it's fully down, we already tested it, if it's mixed case, we don't
-                    // want to test a lowercase version of it.
-                    isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
-                }
+                isInDict = isInDictForAnyCapitalization(dictInfo.mDictionary, text, capitalizeType);
             } finally {
                 if (null != dictInfo) {
                     if (!mDictionaryPool.offer(dictInfo)) {