From 252da38fcd1a40b8c308d6754d644064032094f9 Mon Sep 17 00:00:00 2001
From: Jean Chalard <jchalard@google.com>
Date: Fri, 14 Sep 2012 16:27:04 +0900
Subject: [PATCH] Take locale into account for caps (A10)

Bug: 4967874
Change-Id: Ic7ce7b2de088308fa00865c81246c84c605db1e5
---
 .../android/inputmethod/latin/LatinIME.java   |  2 +-
 .../latin/RichInputConnection.java            |  5 +-
 .../inputmethod/latin/StringUtils.java        | 34 ++++++-----
 .../inputmethod/latin/StringUtilsTests.java   | 61 +++++++++++--------
 4 files changed, 59 insertions(+), 43 deletions(-)

diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 03de03d258..db8f269eb5 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1118,7 +1118,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         // Note: getCursorCapsMode() returns the current capitalization mode that is any
         // combination of CAP_MODE_CHARACTERS, CAP_MODE_WORDS, and CAP_MODE_SENTENCES. 0 means none
         // of them.
-        return mConnection.getCursorCapsMode(inputType);
+        return mConnection.getCursorCapsMode(inputType, mSubtypeSwitcher.getCurrentSubtypeLocale());
     }
 
     // Factor in auto-caps and manual caps and compute the current caps mode.
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 43b9ba7a99..b85f9dcd70 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -30,6 +30,7 @@ import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
 
+import java.util.Locale;
 import java.util.regex.Pattern;
 
 /**
@@ -189,7 +190,7 @@ public class RichInputConnection {
         }
     }
 
-    public int getCursorCapsMode(final int inputType) {
+    public int getCursorCapsMode(final int inputType, final Locale locale) {
         mIC = mParent.getCurrentInputConnection();
         if (null == mIC) return Constants.TextUtils.CAP_MODE_OFF;
         if (!TextUtils.isEmpty(mComposingText)) return Constants.TextUtils.CAP_MODE_OFF;
@@ -204,7 +205,7 @@ public class RichInputConnection {
         }
         // This never calls InputConnection#getCapsMode - in fact, it's a static method that
         // never blocks or initiates IPC.
-        return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType);
+        return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale);
     }
 
     public CharSequence getTextBeforeCursor(final int i, final int j) {
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 0fc6c32d70..6dc1ea807e 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -196,13 +196,14 @@ public final class StringUtils {
      * @param reqModes The modes to be checked: may be any combination of
      * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
      * {@link TextUtils#CAP_MODE_SENTENCES}.
+     * @param locale The locale to consider for capitalization rules
      *
      * @return Returns the actual capitalization modes that can be in effect
      * at the current position, which is any combination of
      * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
      * {@link TextUtils#CAP_MODE_SENTENCES}.
      */
-    public static int getCapsMode(final CharSequence cs, final int reqModes) {
+    public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale) {
         // Quick description of what we want to do:
         // CAP_MODE_CHARACTERS is always on.
         // CAP_MODE_WORDS is on if there is some whitespace before the cursor.
@@ -270,19 +271,24 @@ public final class StringUtils {
         // we know that MODE_SENTENCES is being requested.
 
         // Step 4 : Search for MODE_SENTENCES.
-        for (; j > 0; j--) {
-            // Here we look to go over any closing punctuation. This is because in dominant variants
-            // of English, the final period is placed within double quotes and maybe other closing
-            // punctuation signs.
-            // TODO: this is wrong for almost everything except American typography rules for
-            // English. It's wrong for British typography rules for English, it's wrong for French,
-            // it's wrong for German, it's wrong for Spanish, and possibly everything else.
-            // (note that American rules and British rules have nothing to do with en_US and en_GB,
-            // as both rules are used in both countries - it's merely a name for the set of rules)
-            final char c = cs.charAt(j - 1);
-            if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
-                    && Character.getType(c) != Character.END_PUNCTUATION) {
-                break;
+        // English is a special case in that "American typography" rules, which are the most common
+        // in English, state that a sentence terminator immediately following a quotation mark
+        // should be swapped with it and de-duplicated (included in the quotation mark),
+        // e.g. <<Did he say, "let's go home?">>
+        // No other language has such a rule as far as I know, instead putting inside the quotation
+        // mark as the exact thing quoted and handling the surrounding punctuation independently,
+        // e.g. <<Did he say, "let's go home"?>>
+        // Hence, specifically for English, we treat this special case here.
+        if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
+            for (; j > 0; j--) {
+                // Here we look to go over any closing punctuation. This is because in dominant
+                // variants of English, the final period is placed within double quotes and maybe
+                // other closing punctuation signs. This is generally not true in other languages.
+                final char c = cs.charAt(j - 1);
+                if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
+                        && Character.getType(c) != Character.END_PUNCTUATION) {
+                    break;
+                }
             }
         }
 
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index c3d9c06168..00cca9d3ba 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -19,6 +19,8 @@ package com.android.inputmethod.latin;
 import android.test.AndroidTestCase;
 import android.text.TextUtils;
 
+import java.util.Locale;
+
 public class StringUtilsTests extends AndroidTestCase {
     public void testContainsInArray() {
         assertFalse("empty array", StringUtils.containsInArray("key", new String[0]));
@@ -90,43 +92,50 @@ public class StringUtilsTests extends AndroidTestCase {
                 StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5"));
     }
 
-    private void onePathForCaps(final CharSequence cs, final int expectedResult, final int mask) {
+    private void onePathForCaps(final CharSequence cs, final int expectedResult, final int mask,
+            final Locale l) {
         int oneTimeResult = expectedResult & mask;
-        assertEquals("After >" + cs + "<", oneTimeResult, StringUtils.getCapsMode(cs, mask));
+        assertEquals("After >" + cs + "<", oneTimeResult, StringUtils.getCapsMode(cs, mask, l));
     }
 
-    private void allPathsForCaps(final CharSequence cs, final int expectedResult) {
+    private void allPathsForCaps(final CharSequence cs, final int expectedResult, final Locale l) {
         final int c = TextUtils.CAP_MODE_CHARACTERS;
         final int w = TextUtils.CAP_MODE_WORDS;
         final int s = TextUtils.CAP_MODE_SENTENCES;
-        onePathForCaps(cs, expectedResult, c | w | s);
-        onePathForCaps(cs, expectedResult, w | s);
-        onePathForCaps(cs, expectedResult, c | s);
-        onePathForCaps(cs, expectedResult, c | w);
-        onePathForCaps(cs, expectedResult, c);
-        onePathForCaps(cs, expectedResult, w);
-        onePathForCaps(cs, expectedResult, s);
+        onePathForCaps(cs, expectedResult, c | w | s, l);
+        onePathForCaps(cs, expectedResult, w | s, l);
+        onePathForCaps(cs, expectedResult, c | s, l);
+        onePathForCaps(cs, expectedResult, c | w, l);
+        onePathForCaps(cs, expectedResult, c, l);
+        onePathForCaps(cs, expectedResult, w, l);
+        onePathForCaps(cs, expectedResult, s, l);
     }
 
     public void testGetCapsMode() {
         final int c = TextUtils.CAP_MODE_CHARACTERS;
         final int w = TextUtils.CAP_MODE_WORDS;
         final int s = TextUtils.CAP_MODE_SENTENCES;
-        allPathsForCaps("", c | w | s);
-        allPathsForCaps("Word", c);
-        allPathsForCaps("Word.", c);
-        allPathsForCaps("Word ", c | w);
-        allPathsForCaps("Word. ", c | w | s);
-        allPathsForCaps("Word..", c);
-        allPathsForCaps("Word.. ", c | w | s);
-        allPathsForCaps("Word... ", c | w | s);
-        allPathsForCaps("Word ... ", c | w | s);
-        allPathsForCaps("Word . ", c | w);
-        allPathsForCaps("In the U.S ", c | w);
-        allPathsForCaps("In the U.S. ", c | w);
-        allPathsForCaps("Some stuff (e.g. ", c | w);
-        allPathsForCaps("In the U.S.. ", c | w | s);
-        allPathsForCaps("\"Word.\" ", c | w | s);
-        allPathsForCaps("\"Word\" ", c | w);
+        Locale l = Locale.ENGLISH;
+        allPathsForCaps("", c | w | s, l);
+        allPathsForCaps("Word", c, l);
+        allPathsForCaps("Word.", c, l);
+        allPathsForCaps("Word ", c | w, l);
+        allPathsForCaps("Word. ", c | w | s, l);
+        allPathsForCaps("Word..", c, l);
+        allPathsForCaps("Word.. ", c | w | s, l);
+        allPathsForCaps("Word... ", c | w | s, l);
+        allPathsForCaps("Word ... ", c | w | s, l);
+        allPathsForCaps("Word . ", c | w, l);
+        allPathsForCaps("In the U.S ", c | w, l);
+        allPathsForCaps("In the U.S. ", c | w, l);
+        allPathsForCaps("Some stuff (e.g. ", c | w, l);
+        allPathsForCaps("In the U.S.. ", c | w | s, l);
+        allPathsForCaps("\"Word.\" ", c | w | s, l);
+        allPathsForCaps("\"Word\". ", c | w | s, l);
+        allPathsForCaps("\"Word\" ", c | w, l);
+        l = Locale.FRENCH;
+        allPathsForCaps("\"Word.\" ", c | w, l);
+        allPathsForCaps("\"Word\". ", c | w | s, l);
+        allPathsForCaps("\"Word\" ", c | w, l);
     }
 }
-- 
GitLab