From 77dd59fc2a0db94f0c58e17097fd6f07c1b5db8c Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Sat, 5 Mar 2016 21:46:20 -0800 Subject: [PATCH] Revert "Introduce script matching for enabling default IME subtypes." This reverts commit 9e7a1c9824cffca32fd7d58bb13bd3416ab32091. Seems that that CL causes ArrayIndexOutOfBoundsException when initializing InputMethodManagerService, which results in an infinite boot animation. Bug: 27129703 Bug: 27348943 Change-Id: I474a87876670ac018c675ac7b4608e90fbb2434b --- .../android/internal/inputmethod/LocaleUtils.java | 214 +++++++-------------- .../internal/inputmethod/LocaleUtilsTest.java | 161 ---------------- 2 files changed, 74 insertions(+), 301 deletions(-) diff --git a/core/java/com/android/internal/inputmethod/LocaleUtils.java b/core/java/com/android/internal/inputmethod/LocaleUtils.java index dc33d509d0d7..99bb4cbea14a 100644 --- a/core/java/com/android/internal/inputmethod/LocaleUtils.java +++ b/core/java/com/android/internal/inputmethod/LocaleUtils.java @@ -18,17 +18,15 @@ package com.android.internal.inputmethod; import com.android.internal.annotations.VisibleForTesting; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; -import android.icu.util.ULocale; +import android.text.TextUtils; import android.util.LocaleList; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Objects; public final class LocaleUtils { @@ -38,120 +36,12 @@ public final class LocaleUtils { Locale get(@Nullable T source); } - /** - * Calculates a matching score for the single desired locale. - * - *

See {@link LocaleUtils#calculateMatchingScore(ULocale, LocaleList, byte[])} for - * details.

- */ - @IntRange(from=1, to=3) - private static byte calculateMatchingSubScore(@NonNull final ULocale supported, - @NonNull final ULocale desired) { - - // Assuming supported/desired is fully expanded. - if (supported.equals(desired)) { - return 3; // Exact match. - } - - // Skip language matching since it was already done in calculateMatchingScore. - - final String supportedScript = supported.getScript(); - if (supportedScript.isEmpty() || !supportedScript.equals(desired.getScript())) { - // TODO: Need subscript matching. For example, Hanb should match with Bopo. - return 1; - } - - final String supportedCountry = supported.getCountry(); - if (supportedCountry.isEmpty() || !supportedCountry.equals(desired.getCountry())) { - return 2; - } - - // Ignore others e.g. variants, extensions. - return 3; - } - - /** - * Calculates a matching score for the desired locale list. - * - *

The supported locale gets a matching score of 3 if all language, script and country of the - * supported locale matches with the desired locale. The supported locale gets a matching - * score of 2 if the language and script of the supported locale matches with the desired - * locale. The supported locale gets a matching score of 1 if only language of the supported - * locale matches with the desired locale. The supported locale gets a matching score of 0 if - * the language of the supported locale doesn't match with the desired locale.

- * - *

This function returns {@code false} if supported locale doesn't match with any desired - * locale list. Otherwise, this function returns {@code true}.

- */ - private static boolean calculateMatchingScore(@NonNull final ULocale supported, - @NonNull final LocaleList desired, @NonNull byte[] out) { - if (desired.isEmpty()) { - return false; - } - - boolean allZeros = true; - for (int i = 0; i < desired.size(); ++i) { - final Locale loc = desired.get(i); - - if (!loc.getLanguage().equals(supported.getLanguage())) { - // TODO: cache the result of addLikelySubtags if it is slow. - out[i] = 0; - } else { - out[i] = calculateMatchingSubScore( - supported, ULocale.addLikelySubtags(ULocale.forLocale(loc))); - if (allZeros && out[i] != 0) { - allZeros = false; - } - } - } - return !allZeros; - } - - private static final class ScoreEntry implements Comparable { - public int index = -1; - @NonNull public byte[] score; // matching score of the i-th system languages. - - ScoreEntry(int capacity) { - score = new byte[capacity]; - } - - /** - * Update score and index if the given score is better than this. - */ - public void updateIfBetter(@NonNull byte[] newScore, int newIndex) { - if (isBetterThan(score) != 1) { - return; - } - - for (int i = 0; i < score.length; ++i) { - score[i] = newScore[i]; - } - index = newIndex; - } - - /** - * Determines given score is better than current. - * - *

Compares the matching score for the first priority locale. If the given score has - * higher score than current score, returns 1. If the current score has higher score than - * given score, returns -1. Otherwise, do the same comparison for the next priority locale. - * If given score and current score is same for the all system locale, returns 0.

- */ - private int isBetterThan(@NonNull byte[] other) { - for (int i = 0; i < score.length; ++i) { - if (score[i] < other[i]) { - return 1; - } else if (score[i] > other[i]) { - return -1; - } - } - return 0; - } - - @Override - public int compareTo(ScoreEntry other) { - return isBetterThan(score); + @Nullable + private static String getLanguage(@Nullable Locale locale) { + if (locale == null) { + return null; } + return locale.getLanguage(); } /** @@ -162,8 +52,14 @@ public final class LocaleUtils { * {@code "en-GB", "ja", "en-AU", "fr-CA", "en-IN"} is specified to {@code preferredLanguages}, * this method tries to copy at most one English locale, at most one Japanese, and at most one * French locale from {@code source} to {@code dest}. Here the best matching English locale - * will be searched from {@code source} based on matching score. For the score design, see - * {@link LocaleUtils#calculateMatchingScore(ULocale, LocaleList, byte[])}

+ * will be searched from {@code source} as follows. + *
    + *
  1. The first instance in {@code sources} that exactly matches {@code "en-GB"}
  2. + *
  3. The first instance in {@code sources} that exactly matches {@code "en-AU"}
  4. + *
  5. The first instance in {@code sources} that exactly matches {@code "en-IN"}
  6. + *
  7. The first instance in {@code sources} that partially matches {@code "en"}
  8. + *
+ *

Then this method iterates the same algorithm for Japanese then French.

* * @param sources Source items to be filtered. * @param extractor Type converter from the source items to {@link Locale} object. @@ -178,31 +74,69 @@ public final class LocaleUtils { @NonNull LocaleExtractor extractor, @NonNull LocaleList preferredLanguages, @NonNull ArrayList dest) { - final HashMap scoreboard = new HashMap<>(); - final byte[] score = new byte[preferredLanguages.size()]; - - for (int i = 0; i < sources.size(); ++i) { - final Locale loc = extractor.get(sources.get(i)); - if (loc == null || - !calculateMatchingScore(ULocale.addLikelySubtags(ULocale.forLocale(loc)), - preferredLanguages, score)) { - continue; + final Locale[] availableLocales = new Locale[sources.size()]; + for (int i = 0; i < availableLocales.length; ++i) { + availableLocales[i] = extractor.get(sources.get(i)); + } + final Locale[] sortedPreferredLanguages = new Locale[preferredLanguages.size()]; + if (sortedPreferredLanguages.length > 0) { + int nextIndex = 0; + final int N = preferredLanguages.size(); + languageLoop: + for (int i = 0; i < N; ++i) { + final String language = getLanguage(preferredLanguages.get(i)); + for (int j = 0; j < nextIndex; ++j) { + if (TextUtils.equals(getLanguage(sortedPreferredLanguages[j]), language)) { + continue languageLoop; + } + } + for (int j = i; j < N; ++j) { + final Locale locale = preferredLanguages.get(j); + if (TextUtils.equals(language, getLanguage(locale))) { + sortedPreferredLanguages[nextIndex] = locale; + ++nextIndex; + } + } } + } + - final String lang = loc.getLanguage(); - ScoreEntry bestScore = scoreboard.get(lang); - if (bestScore == null) { - bestScore = new ScoreEntry(score.length); - scoreboard.put(lang, bestScore); + for (int languageIndex = 0; languageIndex < sortedPreferredLanguages.length;) { + // Finding the range. + final String language = getLanguage(sortedPreferredLanguages[languageIndex]); + int nextLanguageIndex = languageIndex; + for (; nextLanguageIndex < sortedPreferredLanguages.length; ++nextLanguageIndex) { + final Locale locale = sortedPreferredLanguages[nextLanguageIndex]; + if (!TextUtils.equals(getLanguage(locale), language)) { + break; + } } - bestScore.updateIfBetter(score, i); - } + // Check exact match + boolean found = false; + for (int i = languageIndex; !found && i < nextLanguageIndex; ++i) { + final Locale locale = sortedPreferredLanguages[i]; + for (int j = 0; j < availableLocales.length; ++j) { + if (!Objects.equals(locale, availableLocales[j])) { + continue; + } + dest.add(sources.get(j)); + found = true; + break; + } + } - final ScoreEntry[] result = scoreboard.values().toArray(new ScoreEntry[scoreboard.size()]); - Arrays.sort(result); - for (final ScoreEntry entry : result) { - dest.add(sources.get(entry.index)); + if (!found) { + // No exact match. Use language match. + for (int j = 0; j < availableLocales.length; ++j) { + if (!TextUtils.equals(language, getLanguage(availableLocales[j]))) { + continue; + } + dest.add(sources.get(j)); + break; + } + } + languageIndex = nextLanguageIndex; } } -} +} \ No newline at end of file diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/LocaleUtilsTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/LocaleUtilsTest.java index a77800181acb..b9c2da75f55c 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/LocaleUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/inputmethod/LocaleUtilsTest.java @@ -191,165 +191,4 @@ public class LocaleUtilsTest extends InstrumentationTestCase { assertEquals(availableLocales.get(1), dest.get(0)); // "en-CA" } } - - @SmallTest - public void testFilterByLanguageFallbackRules() throws Exception { - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-BA")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-CS")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-ME")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-RS")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(7), dest.get(0)); // "sr-Latn-RS" - } - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS-x-android"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-BA")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-CS")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-ME")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-RS")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(7), dest.get(0)); // "sr-Latn-RS" - } - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA-x-android")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS-x-android")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME-x-android")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS-x-android")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-BA-x-android")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-CS-x-android")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-ME-x-android")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-RS-x-android")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(7), dest.get(0)); // "sr-Latn-RS-x-android" - } - - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl")); - availableLocales.add(Locale.forLanguageTag("sr-Latn")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(2), dest.get(0)); // "sr-Latn" - } - - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr")); - availableLocales.add(Locale.forLanguageTag("sr-RS")); - availableLocales.add(Locale.forLanguageTag("sr-Latn")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(0), dest.get(0)); // "sr" - } - - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr")); - availableLocales.add(Locale.forLanguageTag("sr-RS")); - availableLocales.add(Locale.forLanguageTag("sr-Latn")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(2), dest.get(0)); // "sr-Latn" - } - - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr")); - availableLocales.add(Locale.forLanguageTag("sr-RS")); - availableLocales.add(Locale.forLanguageTag("sr-Latn")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(0), dest.get(0)); // "sr" - } - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr-Latn")); - availableLocales.add(Locale.forLanguageTag("sr-RS")); - availableLocales.add(Locale.forLanguageTag("sr")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(1), dest.get(0)); // "sr-RS" - } - - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS")); - availableLocales.add(Locale.forLanguageTag("sr-Latn-RS")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(0), dest.get(0)); // "sr-Cyrl-RS" - } - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("sr-Latn-RS")); - availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - assertEquals(availableLocales.get(0), dest.get(0)); // "sr-Latn-RS" - } - } - - public void testFilterKnownLimitation() throws Exception { - // Following test cases are not for intentional behavior but checks for preventing the - // behavior from becoming worse. - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("ja-Hrkt"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("ja-Jpan")); - availableLocales.add(Locale.forLanguageTag("ja-Hrkt")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - // Should be ja-Jpan since it supports ja-Hrkt and listed before ja-Hrkt. - assertEquals(availableLocales.get(1), dest.get(0)); - } - { - final LocaleList preferredLocales = LocaleList.forLanguageTags("zh-Hani"); - final ArrayList availableLocales = new ArrayList<>(); - availableLocales.add(Locale.forLanguageTag("zh-Hans")); - availableLocales.add(Locale.forLanguageTag("zh-Hant")); - availableLocales.add(Locale.forLanguageTag("zh-Hanb")); - availableLocales.add(Locale.forLanguageTag("zh-Hani")); - final ArrayList dest = new ArrayList<>(); - LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); - assertEquals(1, dest.size()); - // Should be zh-Hans since it supports zh-Hani. Also zh-Hant, zh-Hanb supports zh-Hani. - assertEquals(availableLocales.get(3), dest.get(0)); - } - } } -- 2.11.0