diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
index f45c73f5308fb4ef591e4defc5703e43ca601326..db877ab7a3e1c920d2616623eeb9b20614e8ed37 100644
--- a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
+++ b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
@@ -16,10 +16,12 @@
 
 package com.android.inputmethod.latin;
 
-import java.util.Arrays;
+import android.text.TextUtils;
 
 import com.android.inputmethod.latin.utils.StringUtils;
 
+import java.util.Arrays;
+
 /**
  * Class to represent information of previous words. This class is used to add n-gram entries
  * into binary dictionaries, to get predictions, and to get suggestions.
@@ -37,8 +39,8 @@ public class PrevWordsInfo {
         public static final WordInfo EMPTY_WORD_INFO = new WordInfo(null);
         public static final WordInfo BEGINNING_OF_SENTENCE = new WordInfo();
 
-        // This is an empty string when mIsBeginningOfSentence is true.
-        public final String mWord;
+        // This is an empty char sequence when mIsBeginningOfSentence is true.
+        public final CharSequence mWord;
         // TODO: Have sentence separator.
         // Whether the current context is beginning of sentence or not. This is true when composing
         // at the beginning of an input field or composing a word after a sentence separator.
@@ -50,7 +52,7 @@ public class PrevWordsInfo {
             mIsBeginningOfSentence = true;
         }
 
-        public WordInfo(final String word) {
+        public WordInfo(final CharSequence word) {
             mWord = word;
             mIsBeginningOfSentence = false;
         }
@@ -73,7 +75,7 @@ public class PrevWordsInfo {
                 return mWord == wordInfo.mWord
                         && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence;
             }
-            return mWord.equals(wordInfo.mWord)
+            return TextUtils.equals(mWord, wordInfo.mWord)
                     && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence;
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index a98b0f1560fa6ecd28c3609ae509bda349c67174..8e027e4f9c4578a716f674ec0fda1c8ed342e047 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin.personalization;
 
 import android.content.Context;
+import android.text.TextUtils;
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.Constants;
@@ -60,7 +61,7 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas
     public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
             final PrevWordsInfo prevWordsInfo, final String word, final boolean isValid,
             final int timestamp, final DistracterFilter distracterFilter) {
-        final String prevWord = prevWordsInfo.mPrevWordsInfo[0].mWord;
+        final CharSequence prevWord = prevWordsInfo.mPrevWordsInfo[0].mWord;
         if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
                 (prevWord != null && prevWord.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
             return;
@@ -71,7 +72,7 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas
                 null /* shortcutTarget */, 0 /* shortcutFreq */, false /* isNotAWord */,
                 false /* isBlacklisted */, timestamp, distracterFilter);
         // Do not insert a word as a bigram of itself
-        if (word.equals(prevWord)) {
+        if (TextUtils.equals(word, prevWord)) {
             return;
         }
         if (null != prevWord) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 38d72066428425c8f5da6db6ebe220614c8d8fc2..14ab2dbbf320c3268f6b2955c5417995cb10bcb7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -24,7 +24,9 @@ import android.view.textservice.SentenceSuggestionsInfo;
 import android.view.textservice.SuggestionsInfo;
 import android.view.textservice.TextInfo;
 
+import com.android.inputmethod.compat.TextInfoCompatUtils;
 import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Locale;
@@ -42,15 +44,15 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
 
     private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti,
             SentenceSuggestionsInfo ssi) {
-        final String typedText = ti.getText();
-        if (!typedText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
+        final CharSequence typedText = TextInfoCompatUtils.getCharSequenceOrString(ti);
+        if (!typedText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
             return null;
         }
         final int N = ssi.getSuggestionsCount();
         final ArrayList<Integer> additionalOffsets = new ArrayList<>();
         final ArrayList<Integer> additionalLengths = new ArrayList<>();
         final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = new ArrayList<>();
-        String currentWord = null;
+        CharSequence currentWord = null;
         for (int i = 0; i < N; ++i) {
             final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
             final int flags = si.getSuggestionsAttributes();
@@ -59,32 +61,33 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
             }
             final int offset = ssi.getOffsetAt(i);
             final int length = ssi.getLengthAt(i);
-            final String subText = typedText.substring(offset, offset + length);
+            final CharSequence subText = typedText.subSequence(offset, offset + length);
             final PrevWordsInfo prevWordsInfo =
                     new PrevWordsInfo(new PrevWordsInfo.WordInfo(currentWord));
             currentWord = subText;
-            if (!subText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
+            if (!subText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
                 continue;
             }
-            final String[] splitTexts =
-                    subText.split(AndroidSpellCheckerService.SINGLE_QUOTE, -1);
+            final CharSequence[] splitTexts = StringUtils.split(subText,
+                    AndroidSpellCheckerService.SINGLE_QUOTE,
+                    true /* preserveTrailingEmptySegments */ );
             if (splitTexts == null || splitTexts.length <= 1) {
                 continue;
             }
             final int splitNum = splitTexts.length;
             for (int j = 0; j < splitNum; ++j) {
-                final String splitText = splitTexts[j];
+                final CharSequence splitText = splitTexts[j];
                 if (TextUtils.isEmpty(splitText)) {
                     continue;
                 }
-                if (mSuggestionsCache.getSuggestionsFromCache(splitText, prevWordsInfo) == null) {
+                if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString(), prevWordsInfo)
+                        == null) {
                     continue;
                 }
                 final int newLength = splitText.length();
                 // Neither RESULT_ATTR_IN_THE_DICTIONARY nor RESULT_ATTR_LOOKS_LIKE_TYPO
                 final int newFlags = 0;
-                final SuggestionsInfo newSi =
-                        new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY);
+                final SuggestionsInfo newSi = new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY);
                 newSi.setCookieAndSequence(si.getCookie(), si.getSequence());
                 if (DBG) {
                     Log.d(TAG, "Override and remove old span over: " + splitText + ", "
@@ -194,20 +197,22 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
             final int length = textInfos.length;
             final SuggestionsInfo[] retval = new SuggestionsInfo[length];
             for (int i = 0; i < length; ++i) {
-                final String prevWord;
+                final CharSequence prevWord;
                 if (sequentialWords && i > 0) {
-                final String prevWordCandidate = textInfos[i - 1].getText();
-                // Note that an empty string would be used to indicate the initial word
-                // in the future.
-                prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate;
+                    final TextInfo prevTextInfo = textInfos[i - 1];
+                    final CharSequence prevWordCandidate =
+                            TextInfoCompatUtils.getCharSequenceOrString(prevTextInfo);
+                    // Note that an empty string would be used to indicate the initial word
+                    // in the future.
+                    prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate;
                 } else {
                     prevWord = null;
                 }
                 final PrevWordsInfo prevWordsInfo =
                         new PrevWordsInfo(new PrevWordsInfo.WordInfo(prevWord));
-                retval[i] = onGetSuggestionsInternal(textInfos[i], prevWordsInfo, suggestionsLimit);
-                retval[i].setCookieAndSequence(textInfos[i].getCookie(),
-                        textInfos[i].getSequence());
+                final TextInfo textInfo = textInfos[i];
+                retval[i] = onGetSuggestionsInternal(textInfo, prevWordsInfo, suggestionsLimit);
+                retval[i].setCookieAndSequence(textInfo.getCookie(), textInfo.getSequence());
             }
             return retval;
         } finally {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
index 13352f39e33cc519d6a86ba4f1688ddb59e400d1..ae582ea251acb4b2c0457973eaa2edf6820ca001 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
@@ -21,6 +21,7 @@ import android.view.textservice.SentenceSuggestionsInfo;
 import android.view.textservice.SuggestionsInfo;
 import android.view.textservice.TextInfo;
 
+import com.android.inputmethod.compat.TextInfoCompatUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 import com.android.inputmethod.latin.utils.RunInLocale;
@@ -127,7 +128,8 @@ public class SentenceLevelAdapter {
 
     public SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) {
         final WordIterator wordIterator = mWordIterator;
-        final CharSequence originalText = originalTextInfo.getText();
+        final CharSequence originalText =
+                TextInfoCompatUtils.getCharSequenceOrString(originalTextInfo);
         final int cookie = originalTextInfo.getCookie();
         final int start = -1;
         final int end = originalText.length();
@@ -136,8 +138,9 @@ public class SentenceLevelAdapter {
         int wordEnd = wordIterator.getEndOfWord(originalText, wordStart);
         while (wordStart <= end && wordEnd != -1 && wordStart != -1) {
             if (wordEnd >= start && wordEnd > wordStart) {
-                final String query = originalText.subSequence(wordStart, wordEnd).toString();
-                final TextInfo ti = new TextInfo(query, cookie, query.hashCode());
+                CharSequence subSequence = originalText.subSequence(wordStart, wordEnd).toString();
+                final TextInfo ti = TextInfoCompatUtils.newInstance(subSequence, 0,
+                        subSequence.length(), cookie, subSequence.hashCode());
                 wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd));
             }
             wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd);
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
index 9ec19efa8dc42eba1907b07e1aea0ab2d628206f..fbce3f2fdeebedbf548cbbd16cb9578968ad5e75 100644
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -43,7 +43,7 @@ public final class LanguageModelParam {
     private static final int BIGRAM_PROBABILITY_FOR_VALID_WORD = 10;
     private static final int BIGRAM_PROBABILITY_FOR_OOV_WORD = Dictionary.NOT_A_PROBABILITY;
 
-    public final String mTargetWord;
+    public final CharSequence mTargetWord;
     public final int[] mWord0;
     public final int[] mWord1;
     // TODO: this needs to be a list of shortcuts
@@ -57,13 +57,13 @@ public final class LanguageModelParam {
     public final int mTimestamp;
 
     // Constructor for unigram. TODO: support shortcuts
-    public LanguageModelParam(final String word, final int unigramProbability,
+    public LanguageModelParam(final CharSequence word, final int unigramProbability,
             final int timestamp) {
         this(null /* word0 */, word, unigramProbability, Dictionary.NOT_A_PROBABILITY, timestamp);
     }
 
     // Constructor for unigram and bigram.
-    public LanguageModelParam(final String word0, final String word1,
+    public LanguageModelParam(final CharSequence word0, final CharSequence word1,
             final int unigramProbability, final int bigramProbability,
             final int timestamp) {
         mTargetWord = word1;