Merge "Use dedicated DictionaryFacilitator for DistracterFilter"
diff --git a/java/res/values/themes-lxx.xml b/java/res/values/themes-lxx.xml
index 03babd2..8016e76 100644
--- a/java/res/values/themes-lxx.xml
+++ b/java/res/values/themes-lxx.xml
@@ -51,7 +51,7 @@
         <item name="functionalKeyBackground">@drawable/btn_keyboard_key_functional_lxx</item>
         <item name="spacebarBackground">@drawable/btn_keyboard_spacebar_lxx</item>
         <item name="spacebarIconWidthRatio">0.9</item>
-        <item name="keyTypeface">bold</item>
+        <item name="keyTypeface">normal</item>
         <item name="keyTextColor">@color/key_text_color_holo</item>
         <item name="keyTextInactivatedColor">@color/key_text_inactive_color_lxx</item>
         <item name="functionalTextColor">@color/key_hint_letter_color_lxx</item>
@@ -116,7 +116,7 @@
         <item name="centerSuggestionPercentile">@fraction/config_center_suggestion_percentile</item>
         <item name="maxMoreSuggestionsRow">@integer/config_max_more_suggestions_row</item>
         <item name="minMoreSuggestionsWidth">@fraction/config_min_more_suggestions_width</item>
-        <item name="android:background">@drawable/keyboard_suggest_strip_holo</item>
+        <item name="android:background">@color/suggestions_strip_background_lxx</item>
         <item name="suggestionStripOptions">autoCorrectBold|validTypedWordBold</item>
         <item name="colorValidTypedWord">@color/typed_word_color_lxx</item>
         <item name="colorTypedWord">@color/typed_word_color_lxx</item>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
index e0b74fa..0c0c162 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
@@ -29,8 +29,8 @@
 public final class KeyboardTheme {
     private static final String TAG = KeyboardTheme.class.getSimpleName();
 
-    static final String KITKAT_KEYBOARD_THEME_KEY = "pref_keyboard_layout_20110916";
-    static final String KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509";
+    static final String KLP_KEYBOARD_THEME_KEY = "pref_keyboard_layout_20110916";
+    static final String LXX_KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509";
 
     static final int THEME_ID_ICS = 0;
     static final int THEME_ID_KLP = 2;
@@ -39,9 +39,9 @@
 
     private static final KeyboardTheme[] KEYBOARD_THEMES = {
         new KeyboardTheme(THEME_ID_ICS, R.style.KeyboardTheme_ICS,
-                VERSION_CODES.ICE_CREAM_SANDWICH),
+                VERSION_CODES.BASE),
         new KeyboardTheme(THEME_ID_KLP, R.style.KeyboardTheme_KLP,
-                VERSION_CODES.KITKAT),
+                VERSION_CODES.ICE_CREAM_SANDWICH),
         new KeyboardTheme(THEME_ID_LXX, R.style.KeyboardTheme_LXX,
                 // TODO: Update this constant once the *next* version becomes available.
                 VERSION_CODES.CUR_DEVELOPMENT),
@@ -116,22 +116,23 @@
 
     static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs,
             final int sdkVersion) {
-        final String obsoleteIdString = prefs.getString(KITKAT_KEYBOARD_THEME_KEY, null);
-        if (obsoleteIdString != null) {
-            // Remove old preference.
-            prefs.edit().remove(KITKAT_KEYBOARD_THEME_KEY).apply();
+        final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null);
+        if (klpThemeIdString != null) {
             if (sdkVersion <= VERSION_CODES.KITKAT) {
                 try {
-                    final int themeId = Integer.parseInt(obsoleteIdString);
+                    final int themeId = Integer.parseInt(klpThemeIdString);
                     final KeyboardTheme theme = searchKeyboardThemeById(themeId);
                     if (theme != null) {
                         return theme;
                     }
-                    Log.w(TAG, "Unknown keyboard theme in preference: " + obsoleteIdString);
+                    Log.w(TAG, "Unknown keyboard theme in KLP preference: " + klpThemeIdString);
                 } catch (final NumberFormatException e) {
-                    Log.w(TAG, "Illegal keyboard theme in preference: " + obsoleteIdString);
+                    Log.w(TAG, "Illegal keyboard theme in KLP preference: " + klpThemeIdString, e);
                 }
             }
+            // Remove old preference.
+            Log.i(TAG, "Remove KLP keyboard theme preference: " + klpThemeIdString);
+            prefs.edit().remove(KLP_KEYBOARD_THEME_KEY).apply();
         }
         // TODO: This search algorithm isn't optimal if there are many themes.
         for (final KeyboardTheme theme : KEYBOARD_THEMES) {
@@ -144,27 +145,43 @@
 
     public static void saveKeyboardThemeId(final String themeIdString,
             final SharedPreferences prefs) {
-        prefs.edit().putString(KEYBOARD_THEME_KEY, themeIdString).apply();
+        saveKeyboardThemeId(themeIdString, prefs, getSdkVersion());
+    }
+
+    static String getPreferenceKey(final int sdkVersion) {
+        if (sdkVersion <= VERSION_CODES.KITKAT) {
+            return KLP_KEYBOARD_THEME_KEY;
+        }
+        return LXX_KEYBOARD_THEME_KEY;
+    }
+
+    static void saveKeyboardThemeId(final String themeIdString, final SharedPreferences prefs,
+            final int sdkVersion) {
+        final String prefKey = getPreferenceKey(sdkVersion);
+        prefs.edit().putString(prefKey, themeIdString).apply();
     }
 
     public static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs) {
-        final int sdkVersion = getSdkVersion();
-        final String themeIdString = prefs.getString(KEYBOARD_THEME_KEY, null);
-        if (themeIdString == null) {
+        return getKeyboardTheme(prefs, getSdkVersion());
+    }
+
+    static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion) {
+        final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null);
+        if (lxxThemeIdString == null) {
             return getDefaultKeyboardTheme(prefs, sdkVersion);
         }
         try {
-            final int themeId = Integer.parseInt(themeIdString);
+            final int themeId = Integer.parseInt(lxxThemeIdString);
             final KeyboardTheme theme = searchKeyboardThemeById(themeId);
             if (theme != null) {
                 return theme;
             }
-            Log.w(TAG, "Unknown keyboard theme in preference: " + themeIdString);
+            Log.w(TAG, "Unknown keyboard theme in LXX preference: " + lxxThemeIdString);
         } catch (final NumberFormatException e) {
-            Log.w(TAG, "Illegal keyboard theme in preference: " + themeIdString);
+            Log.w(TAG, "Illegal keyboard theme in LXX preference: " + lxxThemeIdString, e);
         }
-        // Remove preference.
-        prefs.edit().remove(KEYBOARD_THEME_KEY).apply();
+        // Remove preference that contains unknown or illegal theme id.
+        prefs.edit().remove(LXX_KEYBOARD_THEME_KEY).apply();
         return getDefaultKeyboardTheme(prefs, sdkVersion);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 1e31c96..8a6404b 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -113,11 +113,8 @@
         synchronized(mDicTraverseSessions) {
             DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
             if (traverseSession == null) {
-                traverseSession = mDicTraverseSessions.get(traverseSessionId);
-                if (traverseSession == null) {
-                    traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
-                    mDicTraverseSessions.put(traverseSessionId, traverseSession);
-                }
+                traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
+                mDicTraverseSessions.put(traverseSessionId, traverseSession);
             }
             return traverseSession;
         }
@@ -266,20 +263,10 @@
                 new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo));
     }
 
-
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-            final float[] inOutLanguageWeight) {
-        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
-                additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
-    }
-
-    @Override
-    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
             final int sessionId, final float[] inOutLanguageWeight) {
         if (!isValidDictionary()) {
             return null;
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index cd380db..5253cc3 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -73,6 +73,7 @@
      * @param proximityInfo the object for key proximity. May be ignored by some implementations.
      * @param blockOffensiveWords whether to block potentially offensive words
      * @param additionalFeaturesOptions options about additional features used for the suggestion.
+     * @param sessionId the session id.
      * @param inOutLanguageWeight the language weight used for generating suggestions.
      * inOutLanguageWeight is a float array that has only one element. This can be updated when the
      * different language weight is used.
@@ -83,17 +84,7 @@
     abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-            final float[] inOutLanguageWeight);
-
-    // The default implementation of this method ignores sessionId.
-    // Subclasses that want to use sessionId need to override this method.
-    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-            final int sessionId, final float[] inOutLanguageWeight) {
-        return getSuggestions(composer, prevWord, proximityInfo, blockOffensiveWords,
-                additionalFeaturesOptions, inOutLanguageWeight);
-    }
+            final int sessionId, final float[] inOutLanguageWeight);
 
     /**
      * Checks if the given word occurs in the dictionary
@@ -167,7 +158,7 @@
         public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
                 final String prevWord, final ProximityInfo proximityInfo,
                 final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-                final float[] inOutLanguageWeight) {
+                final int sessionId, final float[] inOutLanguageWeight) {
             return null;
         }
 
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 16173ff..239fd06 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -59,20 +59,20 @@
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-            final float[] inOutLanguageWeight) {
+            final int sessionId, final float[] inOutLanguageWeight) {
         final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
         if (dictionaries.isEmpty()) return null;
         // To avoid creating unnecessary objects, we get the list out of the first
         // dictionary and add the rest to it if not null, hence the get(0)
         ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
                 prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
-                inOutLanguageWeight);
+                sessionId, inOutLanguageWeight);
         if (null == suggestions) suggestions = CollectionUtils.newArrayList();
         final int length = dictionaries.size();
         for (int i = 1; i < length; ++ i) {
             final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
                     prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
-                    inOutLanguageWeight);
+                    sessionId, inOutLanguageWeight);
             if (null != sugg) suggestions.addAll(sugg);
         }
         return suggestions;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
index e0220e1..ddbb196 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -455,7 +455,7 @@
             final Dictionary dictionary = dictionaries.getDict(dictType);
             if (null == dictionary) continue;
             final ArrayList<SuggestedWordInfo> dictionarySuggestions =
-                    dictionary.getSuggestionsWithSessionId(composer, prevWord, proximityInfo,
+                    dictionary.getSuggestions(composer, prevWord, proximityInfo,
                             blockOffensiveWords, additionalFeaturesOptions, sessionId,
                             languageWeight);
             if (null == dictionarySuggestions) continue;
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index e323f0a..4358f84 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -366,7 +366,7 @@
     }
 
     @Override
-    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
             final int sessionId, final float[] inOutLanguageWeight) {
@@ -380,9 +380,9 @@
                     return null;
                 }
                 final ArrayList<SuggestedWordInfo> suggestions =
-                        mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
-                                proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
-                                sessionId, inOutLanguageWeight);
+                        mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
+                                blockOffensiveWords, additionalFeaturesOptions, sessionId,
+                                inOutLanguageWeight);
                 if (mBinaryDictionary.isCorrupted()) {
                     Log.i(TAG, "Dictionary (" + mDictName +") is corrupted. "
                             + "Remove and regenerate it.");
@@ -401,15 +401,6 @@
     }
 
     @Override
-    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-            final float[] inOutLanguageWeight) {
-        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
-                additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
-    }
-
-    @Override
     public boolean isValidWord(final String word) {
         reloadDictionaryIfRequired();
         boolean lockAcquired = false;
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
index 9f61d6c..15b1238 100644
--- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -52,20 +52,12 @@
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-            final float[] inOutLanguageWeight) {
-        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
-                additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
-    }
-
-    @Override
-    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
             final int sessionId, final float[] inOutLanguageWeight) {
         if (mLock.readLock().tryLock()) {
             try {
                 return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
-                        blockOffensiveWords, additionalFeaturesOptions, inOutLanguageWeight);
+                        blockOffensiveWords, additionalFeaturesOptions, sessionId,
+                        inOutLanguageWeight);
             } finally {
                 mLock.readLock().unlock();
             }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index db0a8a8..71355f4 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -94,8 +94,8 @@
             final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
             final int[] additionalFeaturesOptions, final int sequenceNumber,
             final OnGetSuggestedWordsCallback callback) {
-        final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
         final String typedWord = wordComposer.getTypedWord();
+        final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(typedWord);
         final String consideredWord = trailingSingleQuotesCount > 0
                 ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
                 : typedWord;
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index ac69729..9cf71c7 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -149,7 +149,8 @@
     public int copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
             final int[] destination) {
         // lastIndex is exclusive
-        final int lastIndex = mTypedWordCache.length() - trailingSingleQuotesCount();
+        final int lastIndex = mTypedWordCache.length()
+                - StringUtils.getTrailingSingleQuotesCount(mTypedWordCache);
         if (lastIndex <= 0) {
             // The string is empty or contains only single quotes.
             return 0;
@@ -331,15 +332,6 @@
         return mIsFirstCharCapitalized;
     }
 
-    public int trailingSingleQuotesCount() {
-        final int lastIndex = mTypedWordCache.length() - 1;
-        int i = lastIndex;
-        while (i >= 0 && mTypedWordCache.charAt(i) == Constants.CODE_SINGLE_QUOTE) {
-            --i;
-        }
-        return lastIndex - i;
-    }
-
     /**
      * Whether or not all of the user typed chars are upper case
      * @return true if all user typed chars are upper case, false otherwise
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 69d0927..4c23d22 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -327,7 +327,7 @@
                 final ArrayList<SuggestedWordInfo> suggestions =
                         dictInfo.mDictionary.getSuggestions(composer, prevWord,
                                 dictInfo.getProximityInfo(), true /* blockOffensiveWords */,
-                                null /* additionalFeaturesOptions */,
+                                null /* additionalFeaturesOptions */, 0 /* sessionId */,
                                 null /* inOutLanguageWeight */);
                 if (suggestions != null) {
                     for (final SuggestedWordInfo suggestion : suggestions) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index c992643..0be2568 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -54,7 +54,7 @@
                 public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
                         final String prevWord, final ProximityInfo proximityInfo,
                         final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-                        final float[] inOutLanguageWeight) {
+                        final int sessionId, final float[] inOutLanguageWeight) {
                     return noSuggestions;
                 }
                 @Override
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java
index a694bf4..5f6e168 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -38,10 +38,10 @@
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
             final String prevWordForBigrams, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-            final float[] inOutLanguageWeight) {
+            final int sessionId, final float[] inOutLanguageWeight) {
         synchronized (mLock) {
             return super.getSuggestions(codes, prevWordForBigrams, proximityInfo,
-                    blockOffensiveWords, additionalFeaturesOptions, inOutLanguageWeight);
+                    blockOffensiveWords, additionalFeaturesOptions, sessionId, inOutLanguageWeight);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java
index 1a6dd58..0499ad2 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java
@@ -43,10 +43,10 @@
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
             final String prevWordForBigrams, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
-            final float[] inOutLanguageWeight) {
+            final int sessionId, final float[] inOutLanguageWeight) {
         synchronized (mLock) {
             return super.getSuggestions(codes, prevWordForBigrams, proximityInfo,
-                    blockOffensiveWords, additionalFeaturesOptions, inOutLanguageWeight);
+                    blockOffensiveWords, additionalFeaturesOptions, sessionId, inOutLanguageWeight);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index 577910a..d277c9f 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -125,9 +125,10 @@
         coordinates = mKeyboard.getCoordinates(codePoints);
         composer.setComposingWord(codePoints, coordinates, prevWord);
 
-        final int trailingSingleQuotesCount = composer.trailingSingleQuotesCount();
-        final String consideredWord = trailingSingleQuotesCount > 0 ? testedWord.substring(0,
-                testedWord.length() - trailingSingleQuotesCount) : testedWord;
+        final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord);
+        final String consideredWord = trailingSingleQuotesCount > 0 ?
+                testedWord.substring(0, testedWord.length() - trailingSingleQuotesCount) :
+                testedWord;
         final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
         final OnGetSuggestedWordsCallback callback = new OnGetSuggestedWordsCallback() {
             @Override
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index 374badc..73ac9a5 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -538,6 +538,15 @@
                 ? casedText.codePointAt(0) : CODE_UNSPECIFIED;
     }
 
+    public static int getTrailingSingleQuotesCount(final CharSequence charSequence) {
+        final int lastIndex = charSequence.length() - 1;
+        int i = lastIndex;
+        while (i >= 0 && charSequence.charAt(i) == Constants.CODE_SINGLE_QUOTE) {
+            --i;
+        }
+        return lastIndex - i;
+    }
+
     @UsedForTesting
     public static class Stringizer<E> {
         public String stringize(final E element) {
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
index 555b1a4..fb5231b 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
@@ -26,7 +26,13 @@
 public class KeyboardThemeTests extends AndroidTestCase {
     private SharedPreferences mPrefs;
 
+    // TODO: Remove this constant once the *next* version becomes available.
+    private static final int VERSION_CODES_LXX = VERSION_CODES.CUR_DEVELOPMENT;
+
     private static final int THEME_ID_NULL = -1;
+    private static final int THEME_ID_UNKNOWN = -2;
+    private static final int THEME_ID_ILLEGAL = -3;
+    private static final String ILLEGAL_THEME_ID_STRING = "ThisCausesNumberFormatExecption";
     private static final int THEME_ID_ICS = KeyboardTheme.THEME_ID_ICS;
     private static final int THEME_ID_KLP = KeyboardTheme.THEME_ID_KLP;
     private static final int THEME_ID_LXX = KeyboardTheme.THEME_ID_LXX;
@@ -37,57 +43,281 @@
         mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
     }
 
+    /*
+     * Helper functions.
+     */
+
+    private static boolean isValidKeyboardThemeId(final int themeId) {
+        switch (themeId) {
+        case THEME_ID_ICS:
+        case THEME_ID_KLP:
+        case THEME_ID_LXX:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private void setKeyboardThemePreference(final String prefKey, final int themeId) {
+        final String themeIdString = Integer.toString(themeId);
+        if (isValidKeyboardThemeId(themeId) || themeId == THEME_ID_UNKNOWN) {
+            // Set valid theme id to preference.
+            mPrefs.edit().putString(prefKey, themeIdString).apply();
+            return;
+        }
+        if (themeId == THEME_ID_NULL) {
+            // Simulate undefined preference.
+            mPrefs.edit().remove(prefKey).apply();
+            return;
+        }
+        // themeId == THEME_ID_ILLEGAL
+        // Simulate illegal format theme id in preference.
+        mPrefs.edit().putString(prefKey, ILLEGAL_THEME_ID_STRING).apply();
+    }
+
+    private void assertKeyboardTheme(final int sdkVersion, final int expectedThemeId) {
+        assertEquals(expectedThemeId, KeyboardTheme.getKeyboardTheme(mPrefs, sdkVersion).mThemeId);
+    }
+
+    /*
+     * Test keyboard theme preference on the same platform version and the same keyboard version.
+     */
+
+    private void assertKeyboardThemePreference(final int sdkVersion, final int oldThemeId,
+            final int expectedThemeId) {
+        final String prefKey = KeyboardTheme.getPreferenceKey(sdkVersion);
+        setKeyboardThemePreference(prefKey, oldThemeId);
+        assertKeyboardTheme(sdkVersion, expectedThemeId);
+    }
+
+    private void assertKeyboardThemePreferenceOnKlp(final int sdkVersion) {
+        final int defaultThemeId = THEME_ID_KLP;
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_NULL, defaultThemeId);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_ICS, THEME_ID_ICS);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_KLP, THEME_ID_KLP);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_LXX, THEME_ID_LXX);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_UNKNOWN, defaultThemeId);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_ILLEGAL, defaultThemeId);
+    }
+
+    public void testKeyboardThemePreferenceOnKlp() {
+        assertKeyboardThemePreferenceOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH);
+        assertKeyboardThemePreferenceOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH_MR1);
+        assertKeyboardThemePreferenceOnKlp(VERSION_CODES.JELLY_BEAN);
+        assertKeyboardThemePreferenceOnKlp(VERSION_CODES.JELLY_BEAN_MR1);
+        assertKeyboardThemePreferenceOnKlp(VERSION_CODES.JELLY_BEAN_MR2);
+        assertKeyboardThemePreferenceOnKlp(VERSION_CODES.KITKAT);
+    }
+
+    private void assertKeyboardThemePreferenceOnLxx(final int sdkVersion) {
+        final int defaultThemeId = THEME_ID_LXX;
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_NULL, defaultThemeId);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_ICS, THEME_ID_ICS);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_KLP, THEME_ID_KLP);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_LXX, THEME_ID_LXX);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_UNKNOWN, defaultThemeId);
+        assertKeyboardThemePreference(sdkVersion, THEME_ID_ILLEGAL, defaultThemeId);
+    }
+
+    public void testKeyboardThemePreferenceOnLxx() {
+        assertKeyboardThemePreferenceOnLxx(VERSION_CODES_LXX);
+    }
+
+    /*
+     * Test default keyboard theme based on the platform version.
+     */
+
     private void assertDefaultKeyboardTheme(final int sdkVersion, final int oldThemeId,
             final int expectedThemeId) {
-        if (oldThemeId == THEME_ID_NULL) {
-            mPrefs.edit().remove(KeyboardTheme.KITKAT_KEYBOARD_THEME_KEY).apply();
-        } else {
-            final String themeIdString = Integer.toString(oldThemeId);
-            mPrefs.edit().putString(KeyboardTheme.KITKAT_KEYBOARD_THEME_KEY, themeIdString).apply();
-        }
+        final String oldPrefKey = KeyboardTheme.KLP_KEYBOARD_THEME_KEY;
+        setKeyboardThemePreference(oldPrefKey, oldThemeId);
+
         final KeyboardTheme defaultTheme =
                 KeyboardTheme.getDefaultKeyboardTheme(mPrefs, sdkVersion);
+
         assertNotNull(defaultTheme);
         assertEquals(expectedThemeId, defaultTheme.mThemeId);
-        assertFalse(mPrefs.contains(KeyboardTheme.KITKAT_KEYBOARD_THEME_KEY));
+        if (sdkVersion <= VERSION_CODES.KITKAT) {
+            // Old preference must be retained if it is valid. Otherwise it must be pruned.
+            assertEquals(isValidKeyboardThemeId(oldThemeId), mPrefs.contains(oldPrefKey));
+            return;
+        }
+        // Old preference must be removed.
+        assertFalse(mPrefs.contains(oldPrefKey));
     }
 
-    private void assertDefaultKeyboardThemeICS(final int sdkVersion) {
-        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_NULL, THEME_ID_ICS);
-        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ICS, THEME_ID_ICS);
-        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_KLP, THEME_ID_KLP);
-    }
-
-    private void assertDefaultKeyboardThemeKLP(final int sdkVersion) {
+    private void assertDefaultKeyboardThemeOnKlp(final int sdkVersion) {
         assertDefaultKeyboardTheme(sdkVersion, THEME_ID_NULL, THEME_ID_KLP);
         assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ICS, THEME_ID_ICS);
         assertDefaultKeyboardTheme(sdkVersion, THEME_ID_KLP, THEME_ID_KLP);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_UNKNOWN, THEME_ID_KLP);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ILLEGAL, THEME_ID_KLP);
     }
 
-    private void assertDefaultKeyboardThemeLXX(final int sdkVersion) {
+    public void testDefaultKeyboardThemeOnKlp() {
+        assertDefaultKeyboardThemeOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH);
+        assertDefaultKeyboardThemeOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH_MR1);
+        assertDefaultKeyboardThemeOnKlp(VERSION_CODES.JELLY_BEAN);
+        assertDefaultKeyboardThemeOnKlp(VERSION_CODES.JELLY_BEAN_MR1);
+        assertDefaultKeyboardThemeOnKlp(VERSION_CODES.JELLY_BEAN_MR2);
+        assertDefaultKeyboardThemeOnKlp(VERSION_CODES.KITKAT);
+    }
+
+    private void assertDefaultKeyboardThemeOnLxx(final int sdkVersion) {
         // Forced to switch to LXX theme.
         assertDefaultKeyboardTheme(sdkVersion, THEME_ID_NULL, THEME_ID_LXX);
         assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ICS, THEME_ID_LXX);
         assertDefaultKeyboardTheme(sdkVersion, THEME_ID_KLP, THEME_ID_LXX);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_UNKNOWN, THEME_ID_LXX);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ILLEGAL, THEME_ID_LXX);
     }
 
-    public void testDefaultKeyboardThemeICS() {
-        assertDefaultKeyboardThemeICS(VERSION_CODES.ICE_CREAM_SANDWICH);
-        assertDefaultKeyboardThemeICS(VERSION_CODES.ICE_CREAM_SANDWICH_MR1);
+    public void testDefaultKeyboardThemeOnLxx() {
+        assertDefaultKeyboardThemeOnLxx(VERSION_CODES_LXX);
     }
 
-    public void testDefaultKeyboardThemeJB() {
-        assertDefaultKeyboardThemeICS(VERSION_CODES.JELLY_BEAN);
-        assertDefaultKeyboardThemeICS(VERSION_CODES.JELLY_BEAN_MR1);
-        assertDefaultKeyboardThemeICS(VERSION_CODES.JELLY_BEAN_MR2);
+    /*
+     * Test keyboard theme preference while upgrading the keyboard that doesn't support LXX theme
+     * to the keyboard that supports LXX theme.
+     */
+
+    private void assertUpgradeKeyboardToLxxOn(final int sdkVersion, final int oldThemeId,
+            final int expectedThemeId) {
+        setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, oldThemeId);
+        // Clean up new keyboard theme preference to simulate "upgrade to LXX keyboard".
+        setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+
+        final KeyboardTheme theme = KeyboardTheme.getKeyboardTheme(mPrefs, sdkVersion);
+
+        assertNotNull(theme);
+        assertEquals(expectedThemeId, theme.mThemeId);
+        if (sdkVersion <= VERSION_CODES.KITKAT) {
+            // New preference must not exist.
+            assertFalse(mPrefs.contains(KeyboardTheme.LXX_KEYBOARD_THEME_KEY));
+            // Old preference must be retained if it is valid. Otherwise it must be pruned.
+            assertEquals(isValidKeyboardThemeId(oldThemeId),
+                    mPrefs.contains(KeyboardTheme.KLP_KEYBOARD_THEME_KEY));
+            if (isValidKeyboardThemeId(oldThemeId)) {
+                // Old preference must have an expected value.
+                assertEquals(mPrefs.getString(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, null),
+                        Integer.toString(expectedThemeId));
+            }
+            return;
+        }
+        // Old preference must be removed.
+        assertFalse(mPrefs.contains(KeyboardTheme.KLP_KEYBOARD_THEME_KEY));
+        // New preference must not exist.
+        assertFalse(mPrefs.contains(KeyboardTheme.LXX_KEYBOARD_THEME_KEY));
     }
 
-    public void testDefaultKeyboardThemeKLP() {
-        assertDefaultKeyboardThemeKLP(VERSION_CODES.KITKAT);
+    private void assertUpgradeKeyboardToLxxOnKlp(final int sdkVersion) {
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_NULL, THEME_ID_KLP);
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_ICS, THEME_ID_ICS);
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_KLP, THEME_ID_KLP);
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_UNKNOWN, THEME_ID_KLP);
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_ILLEGAL, THEME_ID_KLP);
     }
 
-    public void testDefaultKeyboardThemeLXX() {
-        // TODO: Update this constant once the *next* version becomes available.
-        assertDefaultKeyboardThemeLXX(VERSION_CODES.CUR_DEVELOPMENT);
+    // Upgrading keyboard on I,J and K.
+    public void testUpgradeKeyboardToLxxOnKlp() {
+        assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH);
+        assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.ICE_CREAM_SANDWICH_MR1);
+        assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.JELLY_BEAN);
+        assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.JELLY_BEAN_MR1);
+        assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.JELLY_BEAN_MR2);
+        assertUpgradeKeyboardToLxxOnKlp(VERSION_CODES.KITKAT);
+    }
+
+    private void assertUpgradeKeyboardToLxxOnLxx(final int sdkVersion) {
+        // Forced to switch to LXX theme.
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_NULL, THEME_ID_LXX);
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_ICS, THEME_ID_LXX);
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_KLP, THEME_ID_LXX);
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_UNKNOWN, THEME_ID_LXX);
+        assertUpgradeKeyboardToLxxOn(sdkVersion, THEME_ID_ILLEGAL, THEME_ID_LXX);
+    }
+
+    // Upgrading keyboard on L.
+    public void testUpgradeKeyboardToLxxOnLxx() {
+        assertUpgradeKeyboardToLxxOnLxx(VERSION_CODES_LXX);
+    }
+
+    /*
+     * Test keyboard theme preference while upgrading platform version.
+     */
+
+    private void assertUpgradePlatformFromTo(final int oldSdkVersion, final int newSdkVersion,
+            final int oldThemeId, final int expectedThemeId) {
+        if (newSdkVersion < oldSdkVersion) {
+            // No need to test.
+            return;
+        }
+        // Clean up preferences.
+        setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+        setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+
+        final String oldPrefKey = KeyboardTheme.getPreferenceKey(oldSdkVersion);
+        setKeyboardThemePreference(oldPrefKey, oldThemeId);
+
+        assertKeyboardTheme(newSdkVersion, expectedThemeId);
+    }
+
+    private void assertUpgradePlatformFromKlpToKlp(final int oldSdkVersion,
+            final int newSdkVersion) {
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_NULL, THEME_ID_KLP);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_ICS, THEME_ID_ICS);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_KLP, THEME_ID_KLP);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_UNKNOWN, THEME_ID_KLP);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_ILLEGAL, THEME_ID_KLP);
+    }
+
+    private void assertUpgradePlatformToKlpFrom(final int oldSdkVersion) {
+        assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.ICE_CREAM_SANDWICH);
+        assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.ICE_CREAM_SANDWICH_MR1);
+        assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.JELLY_BEAN);
+        assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.JELLY_BEAN_MR1);
+        assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.JELLY_BEAN_MR2);
+        assertUpgradePlatformFromKlpToKlp(oldSdkVersion, VERSION_CODES.KITKAT);
+    }
+
+    // Update platform from I,J, and K to I,J, and K
+    public void testUpgradePlatformToKlpFromKlp() {
+        assertUpgradePlatformToKlpFrom(VERSION_CODES.ICE_CREAM_SANDWICH);
+        assertUpgradePlatformToKlpFrom(VERSION_CODES.ICE_CREAM_SANDWICH_MR1);
+        assertUpgradePlatformToKlpFrom(VERSION_CODES.JELLY_BEAN);
+        assertUpgradePlatformToKlpFrom(VERSION_CODES.JELLY_BEAN_MR1);
+        assertUpgradePlatformToKlpFrom(VERSION_CODES.JELLY_BEAN_MR2);
+        assertUpgradePlatformToKlpFrom(VERSION_CODES.KITKAT);
+    }
+
+    private void assertUpgradePlatformToLxxFrom(final int oldSdkVersion) {
+        // Forced to switch to LXX theme.
+        final int newSdkVersion = VERSION_CODES_LXX;
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_NULL, THEME_ID_LXX);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_ICS, THEME_ID_LXX);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_KLP, THEME_ID_LXX);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_UNKNOWN, THEME_ID_LXX);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_ILLEGAL, THEME_ID_LXX);
+    }
+
+    // Update platform from I,J, and K to L
+    public void testUpgradePlatformToLxx() {
+        assertUpgradePlatformToLxxFrom(VERSION_CODES.ICE_CREAM_SANDWICH);
+        assertUpgradePlatformToLxxFrom(VERSION_CODES.ICE_CREAM_SANDWICH_MR1);
+        assertUpgradePlatformToLxxFrom(VERSION_CODES.JELLY_BEAN);
+        assertUpgradePlatformToLxxFrom(VERSION_CODES.JELLY_BEAN_MR1);
+        assertUpgradePlatformToLxxFrom(VERSION_CODES.JELLY_BEAN_MR2);
+        assertUpgradePlatformToLxxFrom(VERSION_CODES.KITKAT);
+    }
+
+    // Update platform from L to L.
+    public void testUpgradePlatformToLxxFromLxx() {
+        final int oldSdkVersion = VERSION_CODES_LXX;
+        final int newSdkVersion = VERSION_CODES_LXX;
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_NULL, THEME_ID_LXX);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_ICS, THEME_ID_ICS);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_KLP, THEME_ID_KLP);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_UNKNOWN, THEME_ID_LXX);
+        assertUpgradePlatformFromTo(oldSdkVersion, newSdkVersion, THEME_ID_ILLEGAL, THEME_ID_LXX);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
index 2a4ead3..bdc608a 100644
--- a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
@@ -372,4 +372,14 @@
         assertTrue("copyCodePointsAndReturnCodePointCount throws when array is too small",
                 exceptionHappened);
     }
+
+    public void testGetTrailingSingleQuotesCount() {
+        assertEquals(0, StringUtils.getTrailingSingleQuotesCount(""));
+        assertEquals(1, StringUtils.getTrailingSingleQuotesCount("'"));
+        assertEquals(5, StringUtils.getTrailingSingleQuotesCount("'''''"));
+        assertEquals(0, StringUtils.getTrailingSingleQuotesCount("a"));
+        assertEquals(0, StringUtils.getTrailingSingleQuotesCount("'this"));
+        assertEquals(1, StringUtils.getTrailingSingleQuotesCount("'word'"));
+        assertEquals(0, StringUtils.getTrailingSingleQuotesCount("I'm"));
+    }
 }