Always specify non-null Locale object to SuggestionSpan

Confusingly, specifying a null Locale object to the constructor
of SuggestionSpan does not necessarily mean that
SuggestionSpan#getLocale() returns null.  The constructor in
question also receives Context object, and Context's locale can
be used as a fallback locale to initialize locale of
SuggestionSpan.

With this CL, LatinIME always specify non-null Locale object
when instantiating SuggestionSpan object.  It basically
corresponds to the active main dictionary, but can be
Locale#ROOT when one locale is not determined for some reasons.

BUG: 20435013
Change-Id: I2c152466410327300e7dba4d7ed9a22f57c17c4f
main
Yohei Yukaw 2015-04-17 16:48:10 -07:00
parent 54e891e2bd
commit 490fa47a46
3 changed files with 61 additions and 14 deletions

View File

@ -33,6 +33,7 @@ import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public final class SuggestionSpanUtils { public final class SuggestionSpanUtils {
@ -57,13 +58,12 @@ public final class SuggestionSpanUtils {
@UsedForTesting @UsedForTesting
public static CharSequence getTextWithAutoCorrectionIndicatorUnderline( public static CharSequence getTextWithAutoCorrectionIndicatorUnderline(
final Context context, final String text) { final Context context, final String text, @Nonnull final Locale locale) {
if (TextUtils.isEmpty(text) || OBJ_FLAG_AUTO_CORRECTION == null) { if (TextUtils.isEmpty(text) || OBJ_FLAG_AUTO_CORRECTION == null) {
return text; return text;
} }
final Spannable spannable = new SpannableString(text); final Spannable spannable = new SpannableString(text);
// TODO: Set locale if it is feasible. final SuggestionSpan suggestionSpan = new SuggestionSpan(context, locale,
final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION, null); new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION, null);
spannable.setSpan(suggestionSpan, 0, text.length(), spannable.setSpan(suggestionSpan, 0, text.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
@ -72,7 +72,7 @@ public final class SuggestionSpanUtils {
@UsedForTesting @UsedForTesting
public static CharSequence getTextWithSuggestionSpan(final Context context, public static CharSequence getTextWithSuggestionSpan(final Context context,
final String pickedWord, final SuggestedWords suggestedWords) { final String pickedWord, final SuggestedWords suggestedWords, final Locale locale) {
if (TextUtils.isEmpty(pickedWord) || suggestedWords.isEmpty() if (TextUtils.isEmpty(pickedWord) || suggestedWords.isEmpty()
|| suggestedWords.isPrediction() || suggestedWords.isPunctuationSuggestions()) { || suggestedWords.isPrediction() || suggestedWords.isPunctuationSuggestions()) {
return pickedWord; return pickedWord;
@ -92,8 +92,7 @@ public final class SuggestionSpanUtils {
suggestionsList.add(word.toString()); suggestionsList.add(word.toString());
} }
} }
// TODO: Set locale if it is feasible. final SuggestionSpan suggestionSpan = new SuggestionSpan(context, locale,
final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
suggestionsList.toArray(new String[suggestionsList.size()]), 0 /* flags */, null); suggestionsList.toArray(new String[suggestionsList.size()]), 0 /* flags */, null);
final Spannable spannable = new SpannableString(pickedWord); final Spannable spannable = new SpannableString(pickedWord);
spannable.setSpan(suggestionSpan, 0, pickedWord.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(suggestionSpan, 0, pickedWord.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

View File

@ -60,6 +60,7 @@ import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.TextRange; import com.android.inputmethod.latin.utils.TextRange;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -1902,6 +1903,15 @@ public final class InputLogic {
SuggestedWords.NOT_A_SEQUENCE_NUMBER); SuggestedWords.NOT_A_SEQUENCE_NUMBER);
} }
/**
* @return the {@link Locale} of the {@link #mDictionaryFacilitator} if available. Otherwise
* {@link Locale#ROOT}.
*/
@Nonnull
private Locale getDictionaryFacilitatorLocale() {
return mDictionaryFacilitator != null ? mDictionaryFacilitator.getLocale() : Locale.ROOT;
}
/** /**
* Gets a chunk of text with or the auto-correction indicator underline span as appropriate. * Gets a chunk of text with or the auto-correction indicator underline span as appropriate.
* *
@ -1921,8 +1931,10 @@ public final class InputLogic {
*/ */
// TODO: Shouldn't this go in some *Utils class instead? // TODO: Shouldn't this go in some *Utils class instead?
private CharSequence getTextWithUnderline(final String text) { private CharSequence getTextWithUnderline(final String text) {
// TODO: Locale should be determined based on context and the text given.
return mIsAutoCorrectionIndicatorOn return mIsAutoCorrectionIndicatorOn
? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(mLatinIME, text) ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
mLatinIME, text, getDictionaryFacilitatorLocale())
: text; : text;
} }
@ -2122,9 +2134,11 @@ public final class InputLogic {
Log.d(TAG, "commitChosenWord() : [" + chosenWord + "]"); Log.d(TAG, "commitChosenWord() : [" + chosenWord + "]");
} }
final SuggestedWords suggestedWords = mSuggestedWords; final SuggestedWords suggestedWords = mSuggestedWords;
// TODO: Locale should be determined based on context and the text given.
final Locale locale = getDictionaryFacilitatorLocale();
final CharSequence chosenWordWithSuggestions = final CharSequence chosenWordWithSuggestions =
SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord, SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
suggestedWords); suggestedWords, locale);
if (DebugFlags.DEBUG_ENABLED) { if (DebugFlags.DEBUG_ENABLED) {
long runTimeMillis = System.currentTimeMillis() - startTimeMillis; long runTimeMillis = System.currentTimeMillis() - startTimeMillis;
Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run " Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run "

View File

@ -31,6 +31,8 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import javax.annotation.Nullable;
@SmallTest @SmallTest
public class SuggestionSpanUtilsTest extends AndroidTestCase { public class SuggestionSpanUtilsTest extends AndroidTestCase {
@ -62,7 +64,7 @@ public class SuggestionSpanUtilsTest extends AndroidTestCase {
private static void assertSuggestionSpan(final String expectedText, private static void assertSuggestionSpan(final String expectedText,
final int reuiredSuggestionSpanFlags, final int requiredSpanFlags, final int reuiredSuggestionSpanFlags, final int requiredSpanFlags,
final String[] expectedSuggestions, final String[] expectedSuggestions, @Nullable final Locale expectedLocale,
final CharSequence actualText) { final CharSequence actualText) {
assertTrue(TextUtils.equals(expectedText, actualText)); assertTrue(TextUtils.equals(expectedText, actualText));
assertTrue(actualText instanceof Spanned); assertTrue(actualText instanceof Spanned);
@ -84,22 +86,39 @@ public class SuggestionSpanUtilsTest extends AndroidTestCase {
assertEquals(expectedSuggestions[i], actualSuggestions[i]); assertEquals(expectedSuggestions[i], actualSuggestions[i]);
} }
} }
// CAVEAT: SuggestionSpan#getLocale() returns String rather than Locale object.
assertEquals(expectedLocale.toString(), suggestionSpan.getLocale());
} }
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
public void testGetTextWithAutoCorrectionIndicatorUnderline() { public void testGetTextWithAutoCorrectionIndicatorUnderline() {
final String ORIGINAL_TEXT = "Hey!"; final String ORIGINAL_TEXT = "Hey!";
final Locale NONNULL_LOCALE = new Locale("en", "GB");
final CharSequence text = SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline( final CharSequence text = SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
getContext(), ORIGINAL_TEXT); getContext(), ORIGINAL_TEXT, NONNULL_LOCALE);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
assertNotSuggestionSpan(ORIGINAL_TEXT, text); assertNotSuggestionSpan(ORIGINAL_TEXT, text);
return; return;
} }
assertSuggestionSpan(ORIGINAL_TEXT, assertSuggestionSpan(ORIGINAL_TEXT,
SuggestionSpan.FLAG_AUTO_CORRECTION /* reuiredSuggestionSpanFlags */, SuggestionSpan.FLAG_AUTO_CORRECTION /* reuiredSuggestionSpanFlags */,
Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* requiredSpanFlags */, Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* requiredSpanFlags */,
new String[]{}, text); new String[]{}, NONNULL_LOCALE, text);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
public void testGetTextWithAutoCorrectionIndicatorUnderlineRootLocale() {
final String ORIGINAL_TEXT = "Hey!";
final CharSequence text = SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
getContext(), ORIGINAL_TEXT, Locale.ROOT);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
assertNotSuggestionSpan(ORIGINAL_TEXT, text);
return;
}
assertSuggestionSpan(ORIGINAL_TEXT,
SuggestionSpan.FLAG_AUTO_CORRECTION /* reuiredSuggestionSpanFlags */,
Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* requiredSpanFlags */,
new String[]{}, Locale.ROOT, text);
} }
public void testGetTextWithSuggestionSpan() { public void testGetTextWithSuggestionSpan() {
@ -119,6 +138,8 @@ public class SuggestionSpanUtilsTest extends AndroidTestCase {
corrections[i] = createWordInfo("correction" + i, SuggestedWordInfo.KIND_CORRECTION); corrections[i] = createWordInfo("correction" + i, SuggestedWordInfo.KIND_CORRECTION);
} }
final Locale NONNULL_LOCALE = new Locale("en", "GB");
// SuggestionSpan will not be attached when {@link SuggestedWords#INPUT_STYLE_PREDICTION} // SuggestionSpan will not be attached when {@link SuggestedWords#INPUT_STYLE_PREDICTION}
// is specified. // is specified.
{ {
@ -132,10 +153,11 @@ public class SuggestionSpanUtilsTest extends AndroidTestCase {
SuggestedWords.INPUT_STYLE_PREDICTION, SuggestedWords.INPUT_STYLE_PREDICTION,
SuggestedWords.NOT_A_SEQUENCE_NUMBER); SuggestedWords.NOT_A_SEQUENCE_NUMBER);
final String PICKED_WORD = prediction2.mWord; final String PICKED_WORD = prediction2.mWord;
// Note that the framework uses the context locale as a fallback locale.
assertNotSuggestionSpan( assertNotSuggestionSpan(
PICKED_WORD, PICKED_WORD,
SuggestionSpanUtils.getTextWithSuggestionSpan(getContext(), PICKED_WORD, SuggestionSpanUtils.getTextWithSuggestionSpan(getContext(), PICKED_WORD,
predictedWords)); predictedWords, NONNULL_LOCALE));
} }
final ArrayList<SuggestedWordInfo> suggestedWordList = new ArrayList<>(); final ArrayList<SuggestedWordInfo> suggestedWordList = new ArrayList<>();
@ -174,13 +196,25 @@ public class SuggestionSpanUtilsTest extends AndroidTestCase {
expectedSuggestions.add(suggestedWord); expectedSuggestions.add(suggestedWord);
} }
// non-null locale
assertSuggestionSpan( assertSuggestionSpan(
PICKED_WORD, PICKED_WORD,
0 /* reuiredSuggestionSpanFlags */, 0 /* reuiredSuggestionSpanFlags */,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* requiredSpanFlags */, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* requiredSpanFlags */,
expectedSuggestions.toArray(new String[expectedSuggestions.size()]), expectedSuggestions.toArray(new String[expectedSuggestions.size()]),
NONNULL_LOCALE,
SuggestionSpanUtils.getTextWithSuggestionSpan(getContext(), PICKED_WORD, SuggestionSpanUtils.getTextWithSuggestionSpan(getContext(), PICKED_WORD,
typedAndCollectedWords)); typedAndCollectedWords, NONNULL_LOCALE));
// root locale
assertSuggestionSpan(
PICKED_WORD,
0 /* reuiredSuggestionSpanFlags */,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* requiredSpanFlags */,
expectedSuggestions.toArray(new String[expectedSuggestions.size()]),
Locale.ROOT,
SuggestionSpanUtils.getTextWithSuggestionSpan(getContext(), PICKED_WORD,
typedAndCollectedWords, Locale.ROOT));
} }
} }