diff options
294 files changed, 4513 insertions, 1794 deletions
diff --git a/common/Android.mk b/common/Android.mk index 085543f75..132a22358 100644 --- a/common/Android.mk +++ b/common/Android.mk @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -LOCAL_PATH:= $(call my-dir) +LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := latinime-common LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/common/src/com/android/inputmethod/latin/common/CodePointUtils.java b/common/src/com/android/inputmethod/latin/common/CodePointUtils.java index 592da5c1f..ec59de850 100644 --- a/common/src/com/android/inputmethod/latin/common/CodePointUtils.java +++ b/common/src/com/android/inputmethod/latin/common/CodePointUtils.java @@ -20,6 +20,8 @@ import com.android.inputmethod.annotations.UsedForTesting; import java.util.Random; +import javax.annotation.Nonnull; + // Utility methods related with code points used for tests. // TODO: Figure out where this class should be. @UsedForTesting @@ -65,17 +67,23 @@ public class CodePointUtils { }; @UsedForTesting - public static int[] generateCodePointSet(final int codePointSetSize, final Random random) { + @Nonnull + public static int[] generateCodePointSet(final int codePointSetSize, + @Nonnull final Random random) { final int[] codePointSet = new int[codePointSetSize]; for (int i = codePointSet.length - 1; i >= 0; ) { final int r = Math.abs(random.nextInt()); - if (r < 0) continue; + if (r < 0) { + continue; + } // Don't insert 0~0x20, but insert any other code point. // Code points are in the range 0~0x10FFFF. final int candidateCodePoint = 0x20 + r % (Character.MAX_CODE_POINT - 0x20); // Code points between MIN_ and MAX_SURROGATE are not valid on their own. if (candidateCodePoint >= Character.MIN_SURROGATE - && candidateCodePoint <= Character.MAX_SURROGATE) continue; + && candidateCodePoint <= Character.MAX_SURROGATE) { + continue; + } codePointSet[i] = candidateCodePoint; --i; } @@ -86,8 +94,10 @@ public class CodePointUtils { * Generates a random word. */ @UsedForTesting - public static String generateWord(final Random random, final int[] codePointSet) { - StringBuilder builder = new StringBuilder(); + @Nonnull + public static String generateWord(@Nonnull final Random random, + @Nonnull final int[] codePointSet) { + final StringBuilder builder = new StringBuilder(); // 8 * 4 = 32 chars max, but we do it the following way so as to bias the random toward // longer words. This should be closer to natural language, and more importantly, it will // exercise the algorithms in dicttool much more. diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/common/src/com/android/inputmethod/latin/common/CollectionUtils.java index f9839eb91..f7ba693af 100644 --- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java +++ b/common/src/com/android/inputmethod/latin/common/CollectionUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.inputmethod.latin.utils; +package com.android.inputmethod.latin.common; import java.util.ArrayList; import java.util.Collection; @@ -22,16 +22,27 @@ import java.util.Collection; import javax.annotation.Nonnull; import javax.annotation.Nullable; +/** + * Utility methods for working with collections. + */ public final class CollectionUtils { private CollectionUtils() { // This utility class is not publicly instantiable. } + /** + * Converts a sub-range of the given array to an ArrayList of the appropriate type. + * @param array Array to be converted. + * @param start First index inclusive to be converted. + * @param end Last index exclusive to be converted. + * @throws IllegalArgumentException if start or end are out of range or start > end. + */ @Nonnull public static <E> ArrayList<E> arrayAsList(@Nonnull final E[] array, final int start, final int end) { if (start < 0 || start > end || end > array.length) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Invalid start: " + start + " end: " + end + + " with array.length: " + array.length); } final ArrayList<E> list = new ArrayList<>(end - start); diff --git a/common/src/com/android/inputmethod/latin/common/ComposedData.java b/common/src/com/android/inputmethod/latin/common/ComposedData.java new file mode 100644 index 000000000..7f0966050 --- /dev/null +++ b/common/src/com/android/inputmethod/latin/common/ComposedData.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.common; + +import javax.annotation.Nonnull; + +/** + * An immutable class that encapsulates a snapshot of word composition data. + */ +public class ComposedData { + @Nonnull + public final InputPointers mInputPointers; + public final boolean mIsBatchMode; + @Nonnull + public final String mTypedWord; + + public ComposedData(@Nonnull final InputPointers inputPointers, final boolean isBatchMode, + @Nonnull final String typedWord) { + mInputPointers = inputPointers; + mIsBatchMode = isBatchMode; + mTypedWord = typedWord; + } + + /** + * Copy the code points in the typed word to a destination array of ints. + * + * If the array is too small to hold the code points in the typed word, nothing is copied and + * -1 is returned. + * + * @param destination the array of ints. + * @return the number of copied code points. + */ + public int copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount( + @Nonnull final int[] destination) { + // lastIndex is exclusive + final int lastIndex = mTypedWord.length() + - StringUtils.getTrailingSingleQuotesCount(mTypedWord); + if (lastIndex <= 0) { + // The string is empty or contains only single quotes. + return 0; + } + + // The following function counts the number of code points in the text range which begins + // at index 0 and extends to the character at lastIndex. + final int codePointSize = Character.codePointCount(mTypedWord, 0, lastIndex); + if (codePointSize > destination.length) { + return -1; + } + return StringUtils.copyCodePointsAndReturnCodePointCount(destination, mTypedWord, 0, + lastIndex, true /* downCase */); + } +} diff --git a/common/src/com/android/inputmethod/latin/common/Constants.java b/common/src/com/android/inputmethod/latin/common/Constants.java index 8f4a1e50d..abc377a84 100644 --- a/common/src/com/android/inputmethod/latin/common/Constants.java +++ b/common/src/com/android/inputmethod/latin/common/Constants.java @@ -18,6 +18,8 @@ package com.android.inputmethod.latin.common; import com.android.inputmethod.annotations.UsedForTesting; +import javax.annotation.Nonnull; + public final class Constants { public static final class Color { /** @@ -259,6 +261,7 @@ public final class Constants { return code >= CODE_SPACE; } + @Nonnull public static String printableCode(final int code) { switch (code) { case CODE_SHIFT: return "shift"; @@ -286,7 +289,8 @@ public final class Constants { } } - public static String printableCodes(final int[] codes) { + @Nonnull + public static String printableCodes(@Nonnull final int[] codes) { final StringBuilder sb = new StringBuilder(); boolean addDelimiter = false; for (final int code : codes) { diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/common/src/com/android/inputmethod/latin/common/CoordinateUtils.java index 3a9705904..031662411 100644 --- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java +++ b/common/src/com/android/inputmethod/latin/common/CoordinateUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.inputmethod.latin.utils; +package com.android.inputmethod.latin.common; import javax.annotation.Nonnull; diff --git a/java/src/com/android/inputmethod/latin/utils/FileUtils.java b/common/src/com/android/inputmethod/latin/common/FileUtils.java index f1106a6c6..676845842 100644 --- a/java/src/com/android/inputmethod/latin/utils/FileUtils.java +++ b/common/src/com/android/inputmethod/latin/common/FileUtils.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.latin.utils; +package com.android.inputmethod.latin.common; import java.io.File; import java.io.FilenameFilter; diff --git a/common/src/com/android/inputmethod/latin/common/InputPointers.java b/common/src/com/android/inputmethod/latin/common/InputPointers.java index 40131aca4..7beee1536 100644 --- a/common/src/com/android/inputmethod/latin/common/InputPointers.java +++ b/common/src/com/android/inputmethod/latin/common/InputPointers.java @@ -18,6 +18,8 @@ package com.android.inputmethod.latin.common; import com.android.inputmethod.annotations.UsedForTesting; +import javax.annotation.Nonnull; + // TODO: This class is not thread-safe. public final class InputPointers { private static final boolean DEBUG_TIME = false; @@ -28,7 +30,7 @@ public final class InputPointers { private final ResizableIntArray mPointerIds; private final ResizableIntArray mTimes; - public InputPointers(int defaultCapacity) { + public InputPointers(final int defaultCapacity) { mDefaultCapacity = defaultCapacity; mXCoordinates = new ResizableIntArray(defaultCapacity); mYCoordinates = new ResizableIntArray(defaultCapacity); @@ -51,7 +53,8 @@ public final class InputPointers { mTimes.fill(lastTime, fromIndex, fillLength); } - public void addPointerAt(int index, int x, int y, int pointerId, int time) { + public void addPointerAt(final int index, final int x, final int y, final int pointerId, + final int time) { mXCoordinates.addAt(index, x); mYCoordinates.addAt(index, y); mPointerIds.addAt(index, pointerId); @@ -62,21 +65,21 @@ public final class InputPointers { } @UsedForTesting - public void addPointer(int x, int y, int pointerId, int time) { + public void addPointer(final int x, final int y, final int pointerId, final int time) { mXCoordinates.add(x); mYCoordinates.add(y); mPointerIds.add(pointerId); mTimes.add(time); } - public void set(InputPointers ip) { + public void set(@Nonnull final InputPointers ip) { mXCoordinates.set(ip.mXCoordinates); mYCoordinates.set(ip.mYCoordinates); mPointerIds.set(ip.mPointerIds); mTimes.set(ip.mTimes); } - public void copy(InputPointers ip) { + public void copy(@Nonnull final InputPointers ip) { mXCoordinates.copy(ip.mXCoordinates); mYCoordinates.copy(ip.mYCoordinates); mPointerIds.copy(ip.mPointerIds); @@ -93,8 +96,9 @@ public final class InputPointers { * @param startPos the starting index of the data in {@code times} and etc. * @param length the number of data to be appended. */ - public void append(int pointerId, ResizableIntArray times, ResizableIntArray xCoordinates, - ResizableIntArray yCoordinates, int startPos, int length) { + public void append(final int pointerId, @Nonnull final ResizableIntArray times, + @Nonnull final ResizableIntArray xCoordinates, + @Nonnull final ResizableIntArray yCoordinates, final int startPos, final int length) { if (length == 0) { return; } @@ -127,14 +131,17 @@ public final class InputPointers { return mXCoordinates.getLength(); } + @Nonnull public int[] getXCoordinates() { return mXCoordinates.getPrimitiveArray(); } + @Nonnull public int[] getYCoordinates() { return mYCoordinates.getPrimitiveArray(); } + @Nonnull public int[] getPointerIds() { return mPointerIds.getPrimitiveArray(); } @@ -145,6 +152,7 @@ public final class InputPointers { * @return The time each point was registered, in milliseconds, relative to the first event in * the sequence. */ + @Nonnull public int[] getTimes() { return mTimes.getPrimitiveArray(); } diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/common/src/com/android/inputmethod/latin/common/LocaleUtils.java index 4f0805c5c..7f2333be5 100644 --- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java +++ b/common/src/com/android/inputmethod/latin/common/LocaleUtils.java @@ -14,15 +14,15 @@ * the License. */ -package com.android.inputmethod.dictionarypack; - -import android.content.res.Configuration; -import android.content.res.Resources; -import android.text.TextUtils; +package com.android.inputmethod.latin.common; import java.util.HashMap; +import java.util.HashSet; import java.util.Locale; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + /** * A class to help with handling Locales in string form. * @@ -105,8 +105,8 @@ public final class LocaleUtils { * @return a constant that measures how well the tested locale matches the reference locale. */ public static int getMatchLevel(final String referenceLocale, final String testedLocale) { - if (TextUtils.isEmpty(referenceLocale)) { - return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH; + if (StringUtils.isEmpty(referenceLocale)) { + return StringUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH; } if (null == testedLocale) return LOCALE_NO_MATCH; final String[] referenceParams = referenceLocale.split("_", 3); @@ -160,45 +160,53 @@ public final class LocaleUtils { return LOCALE_MATCH <= level; } - /** - * Sets the system locale for this process. - * - * @param res the resources to use. Pass current resources. - * @param newLocale the locale to change to. - * @return the old locale. - */ - public static Locale setSystemLocale(final Resources res, final Locale newLocale) { - final Configuration conf = res.getConfiguration(); - final Locale saveLocale = conf.locale; - conf.locale = newLocale; - res.updateConfiguration(conf, res.getDisplayMetrics()); - return saveLocale; - } - private static final HashMap<String, Locale> sLocaleCache = new HashMap<>(); /** * Creates a locale from a string specification. + * @param localeString a string specification of a locale, in a format of "ll_cc_variant" where + * "ll" is a language code, "cc" is a country code. */ - public static Locale constructLocaleFromString(final String localeStr) { - if (localeStr == null) + @Nullable + public static Locale constructLocaleFromString(@Nullable final String localeString) { + if (localeString == null) { return null; + } synchronized (sLocaleCache) { - if (sLocaleCache.containsKey(localeStr)) - return sLocaleCache.get(localeStr); - Locale retval = null; - String[] localeParams = localeStr.split("_", 3); - if (localeParams.length == 1) { - retval = new Locale(localeParams[0]); - } else if (localeParams.length == 2) { - retval = new Locale(localeParams[0], localeParams[1]); - } else if (localeParams.length == 3) { - retval = new Locale(localeParams[0], localeParams[1], localeParams[2]); + if (sLocaleCache.containsKey(localeString)) { + return sLocaleCache.get(localeString); } - if (retval != null) { - sLocaleCache.put(localeStr, retval); + final String[] elements = localeString.split("_", 3); + final Locale locale; + if (elements.length == 1) { + locale = new Locale(elements[0] /* language */); + } else if (elements.length == 2) { + locale = new Locale(elements[0] /* language */, elements[1] /* country */); + } else { // localeParams.length == 3 + locale = new Locale(elements[0] /* language */, elements[1] /* country */, + elements[2] /* variant */); } - return retval; + sLocaleCache.put(localeString, locale); + return locale; } } + + // TODO: Get this information from the framework instead of maintaining here by ourselves. + private static final HashSet<String> sRtlLanguageCodes = new HashSet<>(); + static { + // List of known Right-To-Left language codes. + sRtlLanguageCodes.add("ar"); // Arabic + sRtlLanguageCodes.add("fa"); // Persian + sRtlLanguageCodes.add("iw"); // Hebrew + sRtlLanguageCodes.add("ku"); // Kurdish + sRtlLanguageCodes.add("ps"); // Pashto + sRtlLanguageCodes.add("sd"); // Sindhi + sRtlLanguageCodes.add("ug"); // Uyghur + sRtlLanguageCodes.add("ur"); // Urdu + sRtlLanguageCodes.add("yi"); // Yiddish + } + + public static boolean isRtlLanguage(@Nonnull final Locale locale) { + return sRtlLanguageCodes.contains(locale.getLanguage()); + } } diff --git a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java b/common/src/com/android/inputmethod/latin/common/NativeSuggestOptions.java index 7603dbba5..7ef741cc2 100644 --- a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java +++ b/common/src/com/android/inputmethod/latin/common/NativeSuggestOptions.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.inputmethod.latin.settings; +package com.android.inputmethod.latin.common; public class NativeSuggestOptions { // Need to update suggest_options.h when you add, remove or reorder options. @@ -25,8 +25,11 @@ public class NativeSuggestOptions { private static final int WEIGHT_FOR_LOCALE_IN_THOUSANDS = 4; private static final int OPTIONS_SIZE = 5; - private final int[] mOptions = new int[OPTIONS_SIZE - + AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE]; + private final int[] mOptions; + + public NativeSuggestOptions(final int additionalFeaturesSettingsSize) { + mOptions = new int[OPTIONS_SIZE + additionalFeaturesSettingsSize]; + } public void setIsGesture(final boolean value) { setBooleanOption(IS_GESTURE, value); diff --git a/common/src/com/android/inputmethod/latin/common/ResizableIntArray.java b/common/src/com/android/inputmethod/latin/common/ResizableIntArray.java index ea23d8a33..340abb23e 100644 --- a/common/src/com/android/inputmethod/latin/common/ResizableIntArray.java +++ b/common/src/com/android/inputmethod/latin/common/ResizableIntArray.java @@ -18,8 +18,11 @@ package com.android.inputmethod.latin.common; import java.util.Arrays; +import javax.annotation.Nonnull; + // TODO: This class is not thread-safe. public final class ResizableIntArray { + @Nonnull private int[] mArray; private int mLength; @@ -89,17 +92,18 @@ public final class ResizableIntArray { mLength = 0; } + @Nonnull public int[] getPrimitiveArray() { return mArray; } - public void set(final ResizableIntArray ip) { + public void set(@Nonnull final ResizableIntArray ip) { // TODO: Implement primitive array pool. mArray = ip.mArray; mLength = ip.mLength; } - public void copy(final ResizableIntArray ip) { + public void copy(@Nonnull final ResizableIntArray ip) { final int newCapacity = calculateCapacity(ip.mLength); if (newCapacity > 0) { // TODO: Implement primitive array pool. @@ -109,7 +113,7 @@ public final class ResizableIntArray { mLength = ip.mLength; } - public void append(final ResizableIntArray src, final int startPos, final int length) { + public void append(@Nonnull final ResizableIntArray src, final int startPos, final int length) { if (length == 0) { return; } diff --git a/dictionaries/de_wordlist.combined.gz b/dictionaries/de_wordlist.combined.gz Binary files differindex 803211c01..92c95540c 100644 --- a/dictionaries/de_wordlist.combined.gz +++ b/dictionaries/de_wordlist.combined.gz diff --git a/dictionaries/en_GB_wordlist.combined.gz b/dictionaries/en_GB_wordlist.combined.gz Binary files differindex 1fa9b85ea..217660fc4 100644 --- a/dictionaries/en_GB_wordlist.combined.gz +++ b/dictionaries/en_GB_wordlist.combined.gz diff --git a/dictionaries/en_US_wordlist.combined.gz b/dictionaries/en_US_wordlist.combined.gz Binary files differindex 2e039ff05..8aed9c5e0 100644 --- a/dictionaries/en_US_wordlist.combined.gz +++ b/dictionaries/en_US_wordlist.combined.gz diff --git a/dictionaries/en_wordlist.combined.gz b/dictionaries/en_wordlist.combined.gz Binary files differindex e845346d6..7fe6618cf 100644 --- a/dictionaries/en_wordlist.combined.gz +++ b/dictionaries/en_wordlist.combined.gz diff --git a/dictionaries/es_wordlist.combined.gz b/dictionaries/es_wordlist.combined.gz Binary files differindex 3391e64b4..71e7309fc 100644 --- a/dictionaries/es_wordlist.combined.gz +++ b/dictionaries/es_wordlist.combined.gz diff --git a/dictionaries/fr_wordlist.combined.gz b/dictionaries/fr_wordlist.combined.gz Binary files differindex 1b9fd73f9..afe44a6d9 100644 --- a/dictionaries/fr_wordlist.combined.gz +++ b/dictionaries/fr_wordlist.combined.gz diff --git a/dictionaries/it_wordlist.combined.gz b/dictionaries/it_wordlist.combined.gz Binary files differindex 5a5cbdc7a..ed58a12c5 100644 --- a/dictionaries/it_wordlist.combined.gz +++ b/dictionaries/it_wordlist.combined.gz diff --git a/dictionaries/nl_wordlist.combined.gz b/dictionaries/nl_wordlist.combined.gz Binary files differindex 37ba8ab42..19c3a7ea8 100644 --- a/dictionaries/nl_wordlist.combined.gz +++ b/dictionaries/nl_wordlist.combined.gz diff --git a/dictionaries/pl_wordlist.combined.gz b/dictionaries/pl_wordlist.combined.gz Binary files differindex ba71a5581..2b84eecfd 100644 --- a/dictionaries/pl_wordlist.combined.gz +++ b/dictionaries/pl_wordlist.combined.gz diff --git a/dictionaries/pt_BR_wordlist.combined.gz b/dictionaries/pt_BR_wordlist.combined.gz Binary files differindex 02df1c1ee..7aac61e50 100644 --- a/dictionaries/pt_BR_wordlist.combined.gz +++ b/dictionaries/pt_BR_wordlist.combined.gz diff --git a/dictionaries/pt_PT_wordlist.combined.gz b/dictionaries/pt_PT_wordlist.combined.gz Binary files differindex bcd50ab03..5bf9a60e8 100644 --- a/dictionaries/pt_PT_wordlist.combined.gz +++ b/dictionaries/pt_PT_wordlist.combined.gz diff --git a/dictionaries/ru_wordlist.combined.gz b/dictionaries/ru_wordlist.combined.gz Binary files differindex 401ad08b0..5e9266221 100644 --- a/dictionaries/ru_wordlist.combined.gz +++ b/dictionaries/ru_wordlist.combined.gz diff --git a/dictionaries/sv_wordlist.combined.gz b/dictionaries/sv_wordlist.combined.gz Binary files differindex b6ebab320..db44ae4c4 100644 --- a/dictionaries/sv_wordlist.combined.gz +++ b/dictionaries/sv_wordlist.combined.gz diff --git a/dictionaries/tr_wordlist.combined.gz b/dictionaries/tr_wordlist.combined.gz Binary files differindex 306cea184..d3c8825b9 100644 --- a/dictionaries/tr_wordlist.combined.gz +++ b/dictionaries/tr_wordlist.combined.gz diff --git a/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java b/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java index f80625644..43675682d 100644 --- a/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java +++ b/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java @@ -48,4 +48,10 @@ public final class ProductionFlags { * When {@code true}, personal dictionary sync feature is ready to be enabled. */ public static final boolean ENABLE_PERSONAL_DICTIONARY_SYNC = ENABLE_ACCOUNT_SIGN_IN && false; + + /** + * When {@code true}, the IME maintains per account {@link UserHistoryDictionary}. + */ + public static final boolean ENABLE_PER_ACCOUNT_USER_HISTORY_DICTIONARY = + ENABLE_ACCOUNT_SIGN_IN && false; } diff --git a/java-overridable/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java b/java-overridable/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java index bd54238f8..4e8a10b1f 100644 --- a/java-overridable/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java +++ b/java-overridable/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java @@ -47,6 +47,7 @@ public class AdditionalFeaturesSettingUtils { // do nothing. } + @Nonnull public static RichInputMethodSubtype createRichInputMethodSubtype( @Nonnull final RichInputMethodManager imm, @Nonnull final InputMethodSubtype subtype, diff --git a/java/Android.mk b/java/Android.mk index a2c5697d3..b84b3471e 100644 --- a/java/Android.mk +++ b/java/Android.mk @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -LOCAL_PATH:= $(call my-dir) +LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml index ee1cef6b5..88e867f09 100644 --- a/java/AndroidManifest.xml +++ b/java/AndroidManifest.xml @@ -37,7 +37,6 @@ <application android:label="@string/english_ime_name" android:icon="@drawable/ic_launcher_keyboard" - android:killAfterRestore="false" android:supportsRtl="true" android:allowBackup="true"> diff --git a/java/res/drawable/btn_keyboard_key_ics.xml b/java/res/drawable/btn_keyboard_key_ics.xml index 0bb098d23..bacd5d78f 100644 --- a/java/res/drawable/btn_keyboard_key_ics.xml +++ b/java/res/drawable/btn_keyboard_key_ics.xml @@ -32,6 +32,8 @@ android:drawable="@drawable/btn_keyboard_key_normal_off_holo_dark" /> <!-- Empty background keys. --> + <item android:state_empty="true" android:state_pressed="true" + android:drawable="@drawable/btn_keyboard_key_pressed_ics_light" /> <item android:state_empty="true" android:drawable="@android:color/transparent" /> diff --git a/java/res/drawable/btn_keyboard_key_klp.xml b/java/res/drawable/btn_keyboard_key_klp.xml index 2a202a179..e2f208585 100644 --- a/java/res/drawable/btn_keyboard_key_klp.xml +++ b/java/res/drawable/btn_keyboard_key_klp.xml @@ -32,6 +32,8 @@ android:drawable="@drawable/btn_keyboard_key_normal_off_holo_dark" /> <!-- Empty background keys. --> + <item android:state_empty="true" android:state_pressed="true" + android:drawable="@drawable/btn_keyboard_key_pressed_klp_light" /> <item android:state_empty="true" android:drawable="@android:color/transparent" /> diff --git a/java/res/drawable/btn_keyboard_key_lxx_dark.xml b/java/res/drawable/btn_keyboard_key_lxx_dark.xml index bb1789ae3..161592d3d 100644 --- a/java/res/drawable/btn_keyboard_key_lxx_dark.xml +++ b/java/res/drawable/btn_keyboard_key_lxx_dark.xml @@ -32,8 +32,10 @@ android:drawable="@drawable/btn_keyboard_key_normal_off_lxx_dark" /> <!-- Empty background keys. --> + <item android:state_empty="true" android:state_pressed="true" + android:drawable="@color/key_background_pressed_lxx_dark" /> <item android:state_empty="true" - android:drawable="@color/key_background_lxx_dark" /> + android:drawable="@android:color/transparent" /> <!-- Normal keys. --> <item android:state_pressed="true" diff --git a/java/res/drawable/btn_keyboard_key_lxx_light.xml b/java/res/drawable/btn_keyboard_key_lxx_light.xml index 60fe02dd2..0154d75b4 100644 --- a/java/res/drawable/btn_keyboard_key_lxx_light.xml +++ b/java/res/drawable/btn_keyboard_key_lxx_light.xml @@ -32,8 +32,10 @@ android:drawable="@drawable/btn_keyboard_key_normal_off_lxx_light" /> <!-- Empty background keys. --> + <item android:state_empty="true" android:state_pressed="true" + android:drawable="@color/key_background_pressed_lxx_light" /> <item android:state_empty="true" - android:drawable="@color/key_background_lxx_light" /> + android:drawable="@android:color/transparent" /> <!-- Normal keys. --> <item android:state_pressed="true" diff --git a/java/res/raw/main_de.dict b/java/res/raw/main_de.dict Binary files differindex 45b288375..c3c2cbe46 100644 --- a/java/res/raw/main_de.dict +++ b/java/res/raw/main_de.dict diff --git a/java/res/raw/main_en.dict b/java/res/raw/main_en.dict Binary files differindex 5bbb85761..b9e5bc77b 100644 --- a/java/res/raw/main_en.dict +++ b/java/res/raw/main_en.dict diff --git a/java/res/raw/main_es.dict b/java/res/raw/main_es.dict Binary files differindex fae131850..076d5aa8f 100644 --- a/java/res/raw/main_es.dict +++ b/java/res/raw/main_es.dict diff --git a/java/res/raw/main_fr.dict b/java/res/raw/main_fr.dict Binary files differindex 19532d9bf..0e8686092 100644 --- a/java/res/raw/main_fr.dict +++ b/java/res/raw/main_fr.dict diff --git a/java/res/raw/main_it.dict b/java/res/raw/main_it.dict Binary files differindex ff11b9798..609ef13b7 100644 --- a/java/res/raw/main_it.dict +++ b/java/res/raw/main_it.dict diff --git a/java/res/raw/main_pt_br.dict b/java/res/raw/main_pt_br.dict Binary files differindex 9fa50442a..c33865187 100644 --- a/java/res/raw/main_pt_br.dict +++ b/java/res/raw/main_pt_br.dict diff --git a/java/res/raw/main_ru.dict b/java/res/raw/main_ru.dict Binary files differindex 76b5f805a..d0af70730 100644 --- a/java/res/raw/main_ru.dict +++ b/java/res/raw/main_ru.dict diff --git a/java/res/values-af/strings-emoji-descriptions.xml b/java/res/values-af/strings-emoji-descriptions.xml index 26bfc5f08..5ed066a87 100644 --- a/java/res/values-af/strings-emoji-descriptions.xml +++ b/java/res/values-af/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Koekie"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Staaf sjokolade"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Lekkergoed"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Suiglekker"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Vla"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Heuningpot"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Broskoek"</string> diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml index ca8b816f4..f8be8f1d3 100644 --- a/java/res/values-af/strings.xml +++ b/java/res/values-af/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serwies (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Tradisioneel)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Kompak)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Geen taal nie (alfabet)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string> diff --git a/java/res/values-ar/strings-emoji-descriptions.xml b/java/res/values-ar/strings-emoji-descriptions.xml index 875f626c2..c1decd303 100644 --- a/java/res/values-ar/strings-emoji-descriptions.xml +++ b/java/res/values-ar/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"كعكة"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"بار شيكولاتة"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"حلوى"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"مصاصة"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"كاسترد"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"جرة عسل"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"كعكة بسمن وسكر"</string> diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml index 62d5f3dbd..da414a66d 100644 --- a/java/res/values-ar/strings.xml +++ b/java/res/values-ar/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"هنجليزية (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"الصربية (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (التقليدية)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (مكثفة)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"بدون لغة (أبجدية)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"الأبجدية (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"الأبجدية (QWERTZ)"</string> diff --git a/java/res/values-az-rAZ/strings.xml b/java/res/values-az-rAZ/strings.xml index b13c90536..b1a192ce5 100644 --- a/java/res/values-az-rAZ/strings.xml +++ b/java/res/values-az-rAZ/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hingilis (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serb (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Ənənəvi)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Kompakt)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Dil yoxdur (Əlifba)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Əlifba (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Əlifba (QWERTZ)"</string> diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml index 805014b05..5d7e6b4b1 100644 --- a/java/res/values-bg/strings.xml +++ b/java/res/values-bg/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Хинглиш (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Сръбска (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (традиционна клавиатура)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (компактна)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Без език (латиница)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Латиница (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Латиница (QWERTZ)"</string> diff --git a/java/res/values-bn-rBD/strings.xml b/java/res/values-bn-rBD/strings.xml index 5487bb5f2..8d77be4ea 100644 --- a/java/res/values-bn-rBD/strings.xml +++ b/java/res/values-bn-rBD/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"হিংলিশ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"সার্বিয়ান (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ঐতিহ্যবাহি)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (কম্প্যাক্ট)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"কোনো ভাষা নয় (বর্ণমালা)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"বর্ণমালা (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"বর্ণমালা (QWERTZ)"</string> diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml index ef792b8d0..3f0e447f9 100644 --- a/java/res/values-ca/strings.xml +++ b/java/res/values-ca/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbi (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicional)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (compacte)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Cap idioma (alfabet)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string> diff --git a/java/res/values-cs/strings-emoji-descriptions.xml b/java/res/values-cs/strings-emoji-descriptions.xml index b66420247..b7f359c55 100644 --- a/java/res/values-cs/strings-emoji-descriptions.xml +++ b/java/res/values-cs/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Sušenka"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Tabulka čokolády"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Bonbon"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lízátko"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Pudink"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Hrnek medu"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Dort"</string> diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml index 0e92e4711..39c51e9ea 100644 --- a/java/res/values-da/strings.xml +++ b/java/res/values-da/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbisk (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (traditionelt)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompakt)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Intet sprog (Alfabet)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string> diff --git a/java/res/values-el/strings-emoji-descriptions.xml b/java/res/values-el/strings-emoji-descriptions.xml index aa8966e97..a15d0fdba 100644 --- a/java/res/values-el/strings-emoji-descriptions.xml +++ b/java/res/values-el/strings-emoji-descriptions.xml @@ -269,7 +269,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Κούκι"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Σοκολάτα"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Γλυκά"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Γλειφιτζούρι"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Κρέμα"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Βάζο με μέλι"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Μπισκότο"</string> diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml index 3ed282651..1778cded3 100644 --- a/java/res/values-en-rGB/strings.xml +++ b/java/res/values-en-rGB/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Traditional)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Compact)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"No language (Alphabet)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alphabet (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alphabet (QWERTZ)"</string> diff --git a/java/res/values-en-rIN/strings.xml b/java/res/values-en-rIN/strings.xml index 3ed282651..1778cded3 100644 --- a/java/res/values-en-rIN/strings.xml +++ b/java/res/values-en-rIN/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Traditional)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Compact)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"No language (Alphabet)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alphabet (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alphabet (QWERTZ)"</string> diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml index 8142b1d0b..7b9e3ba66 100644 --- a/java/res/values-es-rUS/strings.xml +++ b/java/res/values-es-rUS/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbio (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicional)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (compacto)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Ningún idioma (alfabeto)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeto (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeto (QWERTZ)"</string> diff --git a/java/res/values-eu-rES/strings-emoji-descriptions.xml b/java/res/values-eu-rES/strings-emoji-descriptions.xml index 2faec968a..c774ae1b8 100644 --- a/java/res/values-eu-rES/strings-emoji-descriptions.xml +++ b/java/res/values-eu-rES/strings-emoji-descriptions.xml @@ -846,6 +846,6 @@ <string name="spoken_emoji_1F6C1" msgid="2845056048320031158">"Bainuontzia"</string> <string name="spoken_emoji_1F6C2" msgid="8117262514698011877">"Pasaporte-kontrola"</string> <string name="spoken_emoji_1F6C3" msgid="1176342001834630675">"Aduana"</string> - <string name="spoken_emoji_1F6C4" msgid="1477622834179978886">"Maleta-erreklamazioa"</string> + <string name="spoken_emoji_1F6C4" msgid="1477622834179978886">"Ekipaje-erreklamazioa"</string> <string name="spoken_emoji_1F6C5" msgid="2495834050856617451">"Ahaztutako maletak"</string> </resources> diff --git a/java/res/values-eu-rES/strings.xml b/java/res/values-eu-rES/strings.xml index 9496d95e9..23d15930c 100644 --- a/java/res/values-eu-rES/strings.xml +++ b/java/res/values-eu-rES/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglisha (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbiarra (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradizionala)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (trinkoa)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Ez dago hizkuntzarik (alfabetoa)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabetoa (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabetoa (QWERTZ)"</string> diff --git a/java/res/values-fa/strings-emoji-descriptions.xml b/java/res/values-fa/strings-emoji-descriptions.xml index cc670eef9..8adb530be 100644 --- a/java/res/values-fa/strings-emoji-descriptions.xml +++ b/java/res/values-fa/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"کلوچه"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"تخته شکلات"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"آبنبات"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"آبنبات چوبی"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"آبنبات چوبی"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"کاستارد"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"ظرف عسل"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"کیک روغنی"</string> diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml index a0390dfdb..16a6f8767 100644 --- a/java/res/values-fa/strings.xml +++ b/java/res/values-fa/strings.xml @@ -84,7 +84,7 @@ <string name="hint_add_to_dictionary_without_word" msgid="3040385779511255101">"برای ذخیره اینجا را لمس کنید"</string> <string name="has_dictionary" msgid="6071847973466625007">"دیکشنری موجود است"</string> <string name="keyboard_layout" msgid="8451164783510487501">"طرح زمینه صفحهکلید"</string> - <string name="switch_accounts" msgid="3321216593719006162">"جابجایی بین حسابها"</string> + <string name="switch_accounts" msgid="3321216593719006162">"جابهجایی بین حسابها"</string> <string name="no_accounts_selected" msgid="2073821619103904330">"هیچ حسابی انتخاب نشده است"</string> <string name="account_selected" msgid="2846876462199625974">"در حال حاضر در حال استفاده از <xliff:g id="EMAIL_ADDRESS">%1$s</xliff:g>"</string> <string name="account_select_ok" msgid="9141195141763227797">"تأیید"</string> @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"هندی انگلیسی (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"صربی (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (سنتی)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (فشرده)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"بدون زبان (حروف الفبا)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"حروف الفبا (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"حروف الفبا (QWERTZ)"</string> diff --git a/java/res/values-fi/strings-emoji-descriptions.xml b/java/res/values-fi/strings-emoji-descriptions.xml index ad08bbda2..7d6a9f957 100644 --- a/java/res/values-fi/strings-emoji-descriptions.xml +++ b/java/res/values-fi/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Pikkuleipä"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Suklaapatukka"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Karamelli"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Tikkukaramelli"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Tikkari"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Vanukas"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Hunajapurkki"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Kakkuviipale"</string> diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml index b133cc9d9..0308d18d7 100644 --- a/java/res/values-fi/strings.xml +++ b/java/res/values-fi/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hindienglanti (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"serbialainen (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (perinteinen)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tiivis)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Ei kieltä (aakkoset)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Aakkoset (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Aakkoset (QWERTZ)"</string> diff --git a/java/res/values-fr-rCA/strings.xml b/java/res/values-fr-rCA/strings.xml index ee522059c..6730a7982 100644 --- a/java/res/values-fr-rCA/strings.xml +++ b/java/res/values-fr-rCA/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbe (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (traditionnel)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (compact)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Aucune langue (alphabet)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alphabet latin (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alphabet latin (QWERTZ)"</string> diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml index 2a93c992d..3349f4a6a 100644 --- a/java/res/values-fr/strings.xml +++ b/java/res/values-fr/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hindi/Anglais (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbe (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (traditionnel)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Compact)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Aucune langue (latin)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alphabet latin (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alphabet latin (QWERTZ)"</string> diff --git a/java/res/values-gl-rES/strings.xml b/java/res/values-gl-rES/strings.xml index d266ad5a6..d72bcb8b1 100644 --- a/java/res/values-gl-rES/strings.xml +++ b/java/res/values-gl-rES/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbio (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicional)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (compacto)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Ningún idioma (alfabeto)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeto (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeto (QWERTZ)"</string> diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml index 9fc08ac8e..a4f2dd39d 100644 --- a/java/res/values-hi/strings.xml +++ b/java/res/values-hi/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"हिंग्लिश (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"सर्बियाई (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (पारंपरिक)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (संक्षिप्त)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"भाषा उपलब्ध नहीं है (लैटिन वर्णाक्षर)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"वर्णाक्षर (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"वर्णाक्षर (QWERTZ)"</string> diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml index 7a5a665b3..e1893714e 100644 --- a/java/res/values-hr/strings.xml +++ b/java/res/values-hr/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Srpski (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicionalni)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompaktna)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Nema jezika (abeceda)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Abeceda (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Abeceda (QWERTZ)"</string> diff --git a/java/res/values-hu/strings-emoji-descriptions.xml b/java/res/values-hu/strings-emoji-descriptions.xml index 4f3d01c86..b72f29853 100644 --- a/java/res/values-hu/strings-emoji-descriptions.xml +++ b/java/res/values-hu/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Sütemény"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Csokoládé"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Cukorka"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Nyalóka"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Sodó"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Mézesbödön"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Tortaszelet"</string> diff --git a/java/res/values-hy-rAM/strings-emoji-descriptions.xml b/java/res/values-hy-rAM/strings-emoji-descriptions.xml index f41f2fc96..dcc718e22 100644 --- a/java/res/values-hy-rAM/strings-emoji-descriptions.xml +++ b/java/res/values-hy-rAM/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Թխվածքաբլիթ"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Շոկոլադի սալիկ"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Կոնֆետ"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Սառնաշաքար կոնֆետ"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Շաքարաքլոր"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Եփովի կրեմ"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Մեղրանոթ"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Տորթի կտոր"</string> diff --git a/java/res/values-hy-rAM/strings.xml b/java/res/values-hy-rAM/strings.xml index 7d301195a..8d7c5c8c1 100644 --- a/java/res/values-hy-rAM/strings.xml +++ b/java/res/values-hy-rAM/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Հինգլիշ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Սերբերեն (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ավանդական)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (սեղմ)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Ոչ մի լեզվով (Այբուբեն)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Այբուբեն (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Այբուբեն (QWERTZ)"</string> diff --git a/java/res/values-is-rIS/strings.xml b/java/res/values-is-rIS/strings.xml index 2bc3bfbeb..8a6927af1 100644 --- a/java/res/values-is-rIS/strings.xml +++ b/java/res/values-is-rIS/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbneskt (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (hefðbundið)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (lítið)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Ekkert tungumál (stafróf)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Stafróf (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Stafróf (QWERTZ)"</string> diff --git a/java/res/values-iw/strings-emoji-descriptions.xml b/java/res/values-iw/strings-emoji-descriptions.xml index ab31403aa..fc4435a14 100644 --- a/java/res/values-iw/strings-emoji-descriptions.xml +++ b/java/res/values-iw/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"עוגייה"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"חפיסת שוקולד"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"סוכרייה"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"סוכרייה על מקל"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"רפרפת"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"סיר דבש"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"עוגת פירות"</string> diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml index 4caaae394..53ef5779f 100644 --- a/java/res/values-ja/strings.xml +++ b/java/res/values-ja/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ヒングリッシュ(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"セルビア語(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>(伝統言語)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>(コンパクト)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"言語なし(アルファベット)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"アルファベット(QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"アルファベット(QWERTZ)"</string> diff --git a/java/res/values-ka-rGE/strings.xml b/java/res/values-ka-rGE/strings.xml index dc2308c2e..d4457a508 100644 --- a/java/res/values-ka-rGE/strings.xml +++ b/java/res/values-ka-rGE/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ჰინგლისური (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"სერბული (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ტრადიციული)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (კომპაქტური)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"ენის გარეშე (ანბანი)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ანბანი (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ანბანი (QWERTZ)"</string> diff --git a/java/res/values-kk-rKZ/strings.xml b/java/res/values-kk-rKZ/strings.xml index 838a07a77..58f8f306e 100644 --- a/java/res/values-kk-rKZ/strings.xml +++ b/java/res/values-kk-rKZ/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Хинглиш (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Серб (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (дәстүрлі)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (шағын)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Тіл жоқ (әліпби)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Әліпби (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Әліпби (QWERTZ)"</string> diff --git a/java/res/values-km-rKH/strings-emoji-descriptions.xml b/java/res/values-km-rKH/strings-emoji-descriptions.xml index 757df50e7..e9b8780a5 100644 --- a/java/res/values-km-rKH/strings-emoji-descriptions.xml +++ b/java/res/values-km-rKH/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"ខូគី"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"សូកូឡា"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"ស្ករគ្រាប់"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"ស្ករគ្រាប់មានដងកាន់"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"សង់ខ្យា"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"ថូ"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"នំខេក"</string> diff --git a/java/res/values-kn-rIN/strings-emoji-descriptions.xml b/java/res/values-kn-rIN/strings-emoji-descriptions.xml index 4e6d5ced5..a013c2755 100644 --- a/java/res/values-kn-rIN/strings-emoji-descriptions.xml +++ b/java/res/values-kn-rIN/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"ಕುಕಿ"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"ಚಾಕೊಲೇಟ್ ಬಾರ್"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"ಕ್ಯಾಂಡಿ"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"ಲಾಲಿಪಪ್"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"ಕಸ್ಟರ್ಡ್"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"ಜೇನಿನ ಮಡಕೆ"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"ಶಾರ್ಟ್ಕೇಕ್"</string> diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml index e3935174e..68195f0d1 100644 --- a/java/res/values-ko/strings.xml +++ b/java/res/values-ko/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"인도 영어(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"세르비아어(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>(번체)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>(컴팩트)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"언어 없음(알파벳)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"알파벳(QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"알파벳(QWERTZ)"</string> diff --git a/java/res/values-ky-rKG/strings.xml b/java/res/values-ky-rKG/strings.xml index 90c2db3b1..6b46785e3 100644 --- a/java/res/values-ky-rKG/strings.xml +++ b/java/res/values-ky-rKG/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Хинглиш (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Сербче (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Салттык)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Чакан)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Тил жок (Алфавит)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Алфавит (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Алфавит (QWERTZ)"</string> diff --git a/java/res/values-lo-rLA/strings-emoji-descriptions.xml b/java/res/values-lo-rLA/strings-emoji-descriptions.xml index 0747fa629..84b9d0502 100644 --- a/java/res/values-lo-rLA/strings-emoji-descriptions.xml +++ b/java/res/values-lo-rLA/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"ຄຸກກີ້"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"ຊັອກໂກແລັດບາ"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"ແຄນດີ້"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"ໂລລິປັອບ"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"ຄັສຕາດ"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"ໝໍ້ນ້ຳເຜິ້ງ"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"ຊັອດເຄັກ"</string> diff --git a/java/res/values-lo-rLA/strings.xml b/java/res/values-lo-rLA/strings.xml index b23eb31b7..efc09bebc 100644 --- a/java/res/values-lo-rLA/strings.xml +++ b/java/res/values-lo-rLA/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ຮິງລິສ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"ເຊີບຽນ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ດັ້ງເດີມ)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ກະທັດຮັດ)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"ບໍ່ມີພາສາ (ໂຕອັກສອນ)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ໂຕອັກສອນ (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ໂຕອັກສອນ (QWERTZ)"</string> diff --git a/java/res/values-lt/strings-emoji-descriptions.xml b/java/res/values-lt/strings-emoji-descriptions.xml index fa81dbbd8..afe9ac0e6 100644 --- a/java/res/values-lt/strings-emoji-descriptions.xml +++ b/java/res/values-lt/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Sausainis"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Šokolado plytelė"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Saldainis"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Ledinukas ant pagaliuko"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Saldus kremas"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Medaus puodynė"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Trapus pyragas"</string> diff --git a/java/res/values-lv/strings-emoji-descriptions.xml b/java/res/values-lv/strings-emoji-descriptions.xml index a51991b81..525a1a0e8 100644 --- a/java/res/values-lv/strings-emoji-descriptions.xml +++ b/java/res/values-lv/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Cepums"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Šokolādes tāfelīte"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Konfekte"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Cukurgailītis"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Olu krēms"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Medus pods"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Smilšu torte ar augļu pildījumu"</string> diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml index ed7e9d457..833839b8a 100644 --- a/java/res/values-lv/strings.xml +++ b/java/res/values-lv/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hindi–angļu valoda (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbu (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicionālā)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompaktā)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Nav valodas (alfabēts)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabēts (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabēts (QWERTZ)"</string> diff --git a/java/res/values-ml-rIN/strings.xml b/java/res/values-ml-rIN/strings.xml index acf00c2b6..a34de64ac 100644 --- a/java/res/values-ml-rIN/strings.xml +++ b/java/res/values-ml-rIN/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ഹിംഗ്ലീഷ് (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"സെർബിയൻ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (പരമ്പരാഗതം)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (കോംപാക്ട്)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"ഭാഷയില്ല (അക്ഷരമാല)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"അക്ഷരമാല (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"അക്ഷരമാല (QWERTZ)"</string> diff --git a/java/res/values-mr-rIN/strings.xml b/java/res/values-mr-rIN/strings.xml index 492226a8c..2c6746e35 100644 --- a/java/res/values-mr-rIN/strings.xml +++ b/java/res/values-mr-rIN/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"हिंग्लिश (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"सर्बियन (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (पारंपारिक)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (संक्षिप्त)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"भाषा नाही (वर्णमाला)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"वर्णमाला (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"वर्णमाला (QWERTZ)"</string> diff --git a/java/res/values-ms-rMY/strings.xml b/java/res/values-ms-rMY/strings.xml index f33bdb4b3..3136c3bbd 100644 --- a/java/res/values-ms-rMY/strings.xml +++ b/java/res/values-ms-rMY/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Bahasa Serbia (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Tradisional)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Sarat)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Tiada bahasa (Abjad)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Abjad (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Abjad (QWERTZ)"</string> diff --git a/java/res/values-my-rMM/strings.xml b/java/res/values-my-rMM/strings.xml index 3e5a491ea..ab4037861 100644 --- a/java/res/values-my-rMM/strings.xml +++ b/java/res/values-my-rMM/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ဟင်ဂလိပ် (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"ဆားဘီယား (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ရိုးရာ)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ကျစ်လစ်သော)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"ဘာသာစကားမရှိ (ဗျည်းအက္ခရာ)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ဗျည်းအက္ခရာ (ကွာတီ)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ဗျည်းအက္ခရာ (ကွာတီ)"</string> diff --git a/java/res/values-nb/strings-emoji-descriptions.xml b/java/res/values-nb/strings-emoji-descriptions.xml index fa655f12f..681118833 100644 --- a/java/res/values-nb/strings-emoji-descriptions.xml +++ b/java/res/values-nb/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Kjeks"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Sjokoladeplate"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Godteri"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Kjærlighet på pinne"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Pudding"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Honningkrukke"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Kakestykke"</string> diff --git a/java/res/values-ne-rNP/strings-emoji-descriptions.xml b/java/res/values-ne-rNP/strings-emoji-descriptions.xml index 43f4d892b..39e5bc075 100644 --- a/java/res/values-ne-rNP/strings-emoji-descriptions.xml +++ b/java/res/values-ne-rNP/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"कुकीज"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"चकलेट बार"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"क्यान्डी"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"लालीपप"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"कस्तार्ड"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"महदानी"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"सर्टकेक"</string> diff --git a/java/res/values-ne-rNP/strings.xml b/java/res/values-ne-rNP/strings.xml index ec17c4e6f..40c2d4f96 100644 --- a/java/res/values-ne-rNP/strings.xml +++ b/java/res/values-ne-rNP/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"हिङ्लिस (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"सर्बियाई (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (परम्परागत)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (संकुचित)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"कुनै भाषा होइन (वर्णमाला)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"वर्णमाला (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"वर्णमाला (QWERTZ)"</string> diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml index 2e0cd3b55..2cc86e12f 100644 --- a/java/res/values-pt/strings.xml +++ b/java/res/values-pt/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Híndi-inglês (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Sérvio (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicional)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (compacto)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Nenhum idioma (alfabeto)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeto (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeto (QWERTZ)"</string> diff --git a/java/res/values-ro/strings-emoji-descriptions.xml b/java/res/values-ro/strings-emoji-descriptions.xml index f44a0b974..2ac84e5a0 100644 --- a/java/res/values-ro/strings-emoji-descriptions.xml +++ b/java/res/values-ro/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Biscuit"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Ciocolată"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Bomboane"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Acadea"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Budincă"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Oală de miere"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Prăjitură"</string> diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml index 8e3ebf2bf..03398733c 100644 --- a/java/res/values-ro/strings.xml +++ b/java/res/values-ro/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Sârbă (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradițională)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Compact)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Nicio limbă (alfabet)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string> diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml index bd45c68a7..e5aaf8ba7 100644 --- a/java/res/values-ru/strings.xml +++ b/java/res/values-ru/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Хинглиш (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Сербский (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (классическая)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (компактная раскладка)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Язык не определен (латиница)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Латиница (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Латиница (QWERTZ)"</string> diff --git a/java/res/values-si-rLK/strings.xml b/java/res/values-si-rLK/strings.xml index 2e5f3d9f4..f8fa5e7c3 100644 --- a/java/res/values-si-rLK/strings.xml +++ b/java/res/values-si-rLK/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"හින්ග්ලිෂ් (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"සර්බියානු (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (සාම්ප්රදායික)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (සංයුක්ත)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"භාෂාවක් නැත (අකාරාදිය)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"අකාරාදිය (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"අකාරාදිය (QWERTZ)"</string> diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml index 80c91c89f..0d0e69fee 100644 --- a/java/res/values-sk/strings.xml +++ b/java/res/values-sk/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"srbčina (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradičná)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompaktná)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Žiadny jazyk (latinka)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Latinka (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Latinka (QWERTZ)"</string> diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml index 396334cdb..e1f86c36c 100644 --- a/java/res/values-sl/strings.xml +++ b/java/res/values-sl/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hindujska angleščina (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Srbščina (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicionalna)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompaktna)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Brez jezika (latinice)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Latinica (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Latinica (QWERTZ)"</string> diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml index 28f6cae4e..f8c4ff601 100644 --- a/java/res/values-sr/strings.xml +++ b/java/res/values-sr/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"хенглески (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"српски (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (традиционални)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (компактна)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Нема језика (абецеда)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Абецеда (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Абецеда (QWERTZ)"</string> diff --git a/java/res/values-sv/strings-emoji-descriptions.xml b/java/res/values-sv/strings-emoji-descriptions.xml index 879de0b90..e49c73ce4 100644 --- a/java/res/values-sv/strings-emoji-descriptions.xml +++ b/java/res/values-sv/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Småkaka"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Chokladkaka"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Godis"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Klubba"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Vaniljkräm"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Honungsburk"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Sockerkaka"</string> diff --git a/java/res/values-sw/strings-emoji-descriptions.xml b/java/res/values-sw/strings-emoji-descriptions.xml index 57163878e..329bf398a 100644 --- a/java/res/values-sw/strings-emoji-descriptions.xml +++ b/java/res/values-sw/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Biskuti"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Mchi wa chokoleti"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Peremende"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Peremende ya kijiti"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Faluda au Kastadi"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Emoji ya chungu cha asali"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Keki tamu yenye vitandamlo inayofanana na biskuti"</string> diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml index ae1a3a8f7..04c4f2bb7 100644 --- a/java/res/values-sw/strings.xml +++ b/java/res/values-sw/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Kiserbia (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (cha Jadi)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Thabiti)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Hakuna lugha (Alfabeti)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeti (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeti (QWERTZ)"</string> diff --git a/java/res/values-ta-rIN/strings.xml b/java/res/values-ta-rIN/strings.xml index 0a2d13890..64c573ba8 100644 --- a/java/res/values-ta-rIN/strings.xml +++ b/java/res/values-ta-rIN/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ஹிங்கிலிஷ் (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"செர்பியன் (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (பாரம்பரியமானது)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (வசதியான)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"மொழியில்லை (அகரவரிசை)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"அகரவரிசை (க்வெர்டி)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"அகரவரிசை (க்வெர்ட்ச்)"</string> diff --git a/java/res/values-te-rIN/strings.xml b/java/res/values-te-rIN/strings.xml index 15e9aaf38..0fb55b4b2 100644 --- a/java/res/values-te-rIN/strings.xml +++ b/java/res/values-te-rIN/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"హింగ్లీష్ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"సెర్బియన్ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (సాంప్రదాయకం)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (కాంపాక్ట్)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"భాష లేదు (ఆల్ఫాబెట్)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ఆల్ఫాబెట్ (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ఆల్ఫాబెట్ (QWERTZ)"</string> diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml index 5c239dbd4..fcc76d9b9 100644 --- a/java/res/values-th/strings.xml +++ b/java/res/values-th/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ภาษาอังกฤษผสมกับฮินดู (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"เซอร์เบีย (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ดั้งเดิม)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (แบบกะทัดรัด)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"ไม่มีภาษา (ตัวอักษรละติน)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ตัวอักษร (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ตัวอักษร (QWERTZ)"</string> diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml index 8d755e42f..82617b889 100644 --- a/java/res/values-tl/strings.xml +++ b/java/res/values-tl/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Traditional)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Compact)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Walang wika (Alpabeto)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alpabeto (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alpabeto (QWERTZ)"</string> diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml index 86426fa1a..359e0d16f 100644 --- a/java/res/values-tr/strings.xml +++ b/java/res/values-tr/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hingilizce (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Sırpça (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Geleneksel)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Kompakt)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Dil yok (Alfabe)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabe (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabe (QWERTZ)"</string> diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml index 33ab58497..dd625cb70 100644 --- a/java/res/values-uk/strings.xml +++ b/java/res/values-uk/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Хінґліш (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"сербська (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (традиційна)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (компактна)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Стандартна (латиниця)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Латиниця (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Латиниця (QWERTZ)"</string> diff --git a/java/res/values-ur-rPK/strings.xml b/java/res/values-ur-rPK/strings.xml index f00e503e0..4b1f03b03 100644 --- a/java/res/values-ur-rPK/strings.xml +++ b/java/res/values-ur-rPK/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ہنگلش (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"سربیائی (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (روایتی)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (کمپیکٹ)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"کوئی زبان نہیں (الفابیٹ)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"حروف تہجی (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"حروف تہجی (QWERTZ)"</string> diff --git a/java/res/values-uz-rUZ/strings.xml b/java/res/values-uz-rUZ/strings.xml index 62143d7af..59aba60f0 100644 --- a/java/res/values-uz-rUZ/strings.xml +++ b/java/res/values-uz-rUZ/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serb (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (an’anaviy)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ixcham)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Til aniqlanmadi (lotin)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Lotin (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Lotin (QWERTZ)"</string> diff --git a/java/res/values-vi/strings-emoji-descriptions.xml b/java/res/values-vi/strings-emoji-descriptions.xml index 8b44dcfbf..492c726d1 100644 --- a/java/res/values-vi/strings-emoji-descriptions.xml +++ b/java/res/values-vi/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"Bánh quy"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"Thanh sôcôla"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"Kẹo"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Kẹo que"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"Món sữa trứng"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"Mắt ong"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"Bánh bơ giòn"</string> diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml index fa0e11b51..ec0deb117 100644 --- a/java/res/values-vi/strings.xml +++ b/java/res/values-vi/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Tiếng Anh-Hindi (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Tiếng Serbia (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Truyền thống)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Viết tắt)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"Không ngôn ngữ nào (Bảng chữ cái)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Bảng chữ cái (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Bảng chữ cái (QWERTZ)"</string> diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml index 4562c4bd1..52b0dac74 100644 --- a/java/res/values-zh-rCN/strings.xml +++ b/java/res/values-zh-rCN/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"印地英语(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"塞尔维亚语(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>布局)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>(传统)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>(紧凑型)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"无语言(字母)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"字母 (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"字母 (QWERTZ)"</string> diff --git a/java/res/values-zh-rHK/strings.xml b/java/res/values-zh-rHK/strings.xml index e584a88fe..840f33387 100644 --- a/java/res/values-zh-rHK/strings.xml +++ b/java/res/values-zh-rHK/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"印度英文 (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"塞爾維亞文 (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (傳統)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (精簡版)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"無語言 (字母)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"字母 (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"字母 (QWERTZ)"</string> diff --git a/java/res/values-zh-rTW/strings-emoji-descriptions.xml b/java/res/values-zh-rTW/strings-emoji-descriptions.xml index 06e260a69..b5c723066 100644 --- a/java/res/values-zh-rTW/strings-emoji-descriptions.xml +++ b/java/res/values-zh-rTW/strings-emoji-descriptions.xml @@ -267,7 +267,7 @@ <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"餅乾"</string> <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"巧克力棒"</string> <string name="spoken_emoji_1F36C" msgid="2168934753998218790">"糖果"</string> - <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"棒棒糖"</string> + <string name="spoken_emoji_1F36D" msgid="3671507903799975792">"Lollipop"</string> <string name="spoken_emoji_1F36E" msgid="4630541402785165902">"卡士達"</string> <string name="spoken_emoji_1F36F" msgid="5577915387425169439">"蜂蜜罐"</string> <string name="spoken_emoji_1F370" msgid="7243244547866114951">"水果蛋糕"</string> diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml index aa534c869..8fac3105e 100644 --- a/java/res/values-zh-rTW/strings.xml +++ b/java/res/values-zh-rTW/strings.xml @@ -102,8 +102,7 @@ <string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"印度英文 (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"塞爾維亞文 (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string> <string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (傳統)"</string> - <!-- no translation found for subtype_generic_compact (3353673321203202922) --> - <skip /> + <string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (精簡)"</string> <string name="subtype_no_language" msgid="7137390094240139495">"無語言 (字母)"</string> <string name="subtype_no_language_qwerty" msgid="244337630616742604">"字母 (QWERTY)"</string> <string name="subtype_no_language_qwertz" msgid="443066912507547976">"字母 (QWERTZ)"</string> diff --git a/java/res/xml-sw600dp/key_space_3kw.xml b/java/res/xml-sw600dp/key_space_3kw.xml index 9932d342e..8cc3a38a5 100644 --- a/java/res/xml-sw600dp/key_space_3kw.xml +++ b/java/res/xml-sw600dp/key_space_3kw.xml @@ -22,12 +22,8 @@ xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" > <switch> - <!-- fa: Perisan - kn: Kannada - ne: Nepali - te: Telugu --> <case - latin:languageCode="fa|kn|ne|te" + latin:keyboardLayoutSet="bengali_akkhor|farsi|kannada|nepali_romanized|nepali_traditional|telugu" latin:languageSwitchKeyEnabled="true" > <Key @@ -39,7 +35,7 @@ latin:keyStyle="zwnjKeyStyle" /> </case> <case - latin:languageCode="fa|kn|ne|te" + latin:keyboardLayoutSet="bengali_akkhor|farsi|kannada|nepali_romanized|nepali_traditional|telugu" latin:languageSwitchKeyEnabled="false" > <Key diff --git a/java/res/xml-sw600dp/key_space_7kw.xml b/java/res/xml-sw600dp/key_space_7kw.xml index 3311f812a..61e076534 100644 --- a/java/res/xml-sw600dp/key_space_7kw.xml +++ b/java/res/xml-sw600dp/key_space_7kw.xml @@ -22,12 +22,8 @@ xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" > <switch> - <!-- fa: Perisan - kn: Kannada - ne: Nepali - te: Telugu --> <case - latin:languageCode="fa|kn|ne|te" + latin:keyboardLayoutSet="bengali_akkhor|farsi|kannada|nepali_romanized|nepali_traditional|telugu" latin:languageSwitchKeyEnabled="true" > <Key @@ -39,7 +35,7 @@ latin:keyStyle="zwnjKeyStyle" /> </case> <case - latin:languageCode="fa|kn|ne|te" + latin:keyboardLayoutSet="bengali_akkhor|farsi|kannada|nepali_romanized|nepali_traditional|telugu" latin:languageSwitchKeyEnabled="false" > <Key diff --git a/java/res/xml-sw600dp/rows_number_normal.xml b/java/res/xml-sw600dp/rows_number_normal.xml index 7a4700d5a..e6fdf73ec 100644 --- a/java/res/xml-sw600dp/rows_number_normal.xml +++ b/java/res/xml-sw600dp/rows_number_normal.xml @@ -24,17 +24,17 @@ <Row> <Key latin:keySpec="-" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key latin:keySpec="+" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key latin:keySpec="." - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key @@ -53,13 +53,15 @@ latin:keyWidth="fillRight" /> </Row> <Row> + <!-- U+FF0A: "*" FULLWIDTH ASTERISK --> <Key - latin:keyStyle="numStarKeyStyle" + latin:keySpec="*|*" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key latin:keySpec="/" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <switch> @@ -70,14 +72,14 @@ latin:keySpec="," latin:keyLabelFlags="hasPopupHint" latin:moreKeys="!text/morekeys_am_pm" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> </case> <default> <Key latin:keySpec="," - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> </default> @@ -100,12 +102,12 @@ <Row> <Key latin:keySpec="(" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key latin:keySpec=")" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <switch> @@ -114,14 +116,14 @@ > <Key latin:keySpec=":" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> </case> <default> <Key latin:keySpec="=" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> </default> @@ -143,8 +145,10 @@ <Key latin:keyStyle="tabletNumSpaceKeyStyle" latin:keyWidth="30%p" /> + <!-- U+FF0A: "*" FULLWIDTH ASTERISK --> <Key - latin:keyStyle="numStarKeyStyle" + latin:keySpec="*|*" + latin:keyStyle="numSymbolKeyStyle" latin:keyXPos="31%p" /> <Key latin:keySpec="0" diff --git a/java/res/xml-sw600dp/rows_number_password.xml b/java/res/xml-sw600dp/rows_number_password.xml index 6c3855a01..37e63383e 100644 --- a/java/res/xml-sw600dp/rows_number_password.xml +++ b/java/res/xml-sw600dp/rows_number_password.xml @@ -70,7 +70,8 @@ <Key latin:keyStyle="deleteKeyStyle" /> <Key - latin:keyStyle="num0KeyStyle" /> + latin:keyStyle="num0KeyStyle" + latin:keyHintLabel="+" /> <Key latin:keyStyle="enterKeyStyle" /> <!-- Note: This Spacer prevents the above key from being marked as a right edge key. --> diff --git a/java/res/xml-sw600dp/rows_phone.xml b/java/res/xml-sw600dp/rows_phone.xml index 612397a90..fc86a7670 100644 --- a/java/res/xml-sw600dp/rows_phone.xml +++ b/java/res/xml-sw600dp/rows_phone.xml @@ -28,16 +28,18 @@ <Row> <Key latin:keySpec="-" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key latin:keySpec="+" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key - latin:keyStyle="numPauseKeyStyle" + latin:keySpec="!string/label_pause_key|," + latin:keyLabelFlags="followKeyLabelRatio|autoXScale" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key @@ -55,16 +57,18 @@ <Row> <Key latin:keySpec="," - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key latin:keySpec="." - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key - latin:keyStyle="numWaitKeyStyle" + latin:keySpec="!string/label_wait_key|;" + latin:keyLabelFlags="followKeyLabelRatio|autoXScale" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key @@ -82,17 +86,17 @@ <Row> <Key latin:keySpec="(" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key latin:keySpec=")" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key latin:keySpec="N" - latin:keyStyle="numKeyStyle" + latin:keyStyle="numSymbolKeyStyle" latin:keyWidth="10%p" latin:backgroundType="functional" /> <Key @@ -109,13 +113,16 @@ <Key latin:keyStyle="tabletNumSpaceKeyStyle" latin:keyWidth="30%p" /> + <!-- U+FF0A: "*" FULLWIDTH ASTERISK --> <Key - latin:keyStyle="numStarKeyStyle" + latin:keySpec="*|*" + latin:keyStyle="numSymbolKeyStyle" latin:keyXPos="31%p" /> <Key - latin:keyStyle="num0KeyStyle" /> + latin:keyStyle="num0KeyStyle" + latin:keyHintLabel="+" /> <Key latin:keySpec="\#" - latin:keyStyle="numKeyStyle" /> + latin:keyStyle="numSymbolKeyStyle" /> </Row> </merge> diff --git a/java/res/xml/key_space_5kw.xml b/java/res/xml/key_space_5kw.xml index b1fe0bbeb..692c245ff 100644 --- a/java/res/xml/key_space_5kw.xml +++ b/java/res/xml/key_space_5kw.xml @@ -22,12 +22,8 @@ xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" > <switch> - <!-- fa: Perisan - kn: Kannada - ne: Nepali - te: Telugu --> <case - latin:languageCode="fa|kn|ne|te" + latin:keyboardLayoutSet="bengali_akkhor|farsi|kannada|nepali_romanized|nepali_traditional|telugu" latin:languageSwitchKeyEnabled="true" > <Key @@ -39,7 +35,7 @@ latin:keyStyle="zwnjKeyStyle" /> </case> <case - latin:languageCode="fa|kn|ne|te" + latin:keyboardLayoutSet="bengali_akkhor|farsi|kannada|nepali_romanized|nepali_traditional|telugu" latin:languageSwitchKeyEnabled="false" > <Key diff --git a/java/res/xml/key_styles_number.xml b/java/res/xml/key_styles_number.xml index 847b43610..911c2763d 100644 --- a/java/res/xml/key_styles_number.xml +++ b/java/res/xml/key_styles_number.xml @@ -33,9 +33,7 @@ latin:keyLabelFlags="fontNormal|followKeyLetterRatio|followFunctionalTextColor" latin:parentStyle="numKeyBaseStyle" /> <key-style - latin:styleName="numFunctionalKeyStyle" - latin:keyLabelFlags="followKeyLargeLetterRatio" - latin:backgroundType="functional" + latin:styleName="numSymbolKeyStyle" latin:parentStyle="numKeyBaseStyle" /> <key-style latin:styleName="numberKeyStyle" @@ -44,7 +42,6 @@ <key-style latin:styleName="num0KeyStyle" latin:keySpec="0" - latin:keyHintLabel="+" latin:parentStyle="numberKeyStyle" /> <key-style latin:styleName="num1KeyStyle" @@ -90,11 +87,6 @@ latin:keySpec="9" latin:keyHintLabel="WXYZ" latin:parentStyle="numberKeyStyle" /> - <!-- U+FF0A: "*" FULLWIDTH ASTERISK --> - <key-style - latin:styleName="numStarKeyStyle" - latin:keySpec="*|*" - latin:parentStyle="numKeyStyle" /> <!-- Only for non-tablet device --> <key-style latin:styleName="numPhoneToSymbolKeyStyle" @@ -105,16 +97,6 @@ latin:keySpec="!text/keylabel_to_phone_numeric|!code/key_switch_alpha_symbol" latin:parentStyle="numModeKeyStyle" /> <key-style - latin:styleName="numPauseKeyStyle" - latin:keySpec="!text/label_pause_key|," - latin:keyLabelFlags="followKeyHintLabelRatio|autoXScale" - latin:parentStyle="numKeyBaseStyle" /> - <key-style - latin:styleName="numWaitKeyStyle" - latin:keySpec="!text/label_wait_key|;" - latin:keyLabelFlags="followKeyHintLabelRatio|autoXScale" - latin:parentStyle="numKeyBaseStyle" /> - <key-style latin:styleName="numTabKeyStyle" latin:keyActionFlags="noKeyPreview" latin:parentStyle="tabKeyStyle" /> diff --git a/java/res/xml/keyboard_layout_set_bengali_akkhor.xml b/java/res/xml/keyboard_layout_set_bengali_akkhor.xml index b2b09b22d..267064d46 100644 --- a/java/res/xml/keyboard_layout_set_bengali_akkhor.xml +++ b/java/res/xml/keyboard_layout_set_bengali_akkhor.xml @@ -19,7 +19,7 @@ --> <KeyboardLayoutSet xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" > - <Feature latin:supportedScript="devanagari" /> + <Feature latin:supportedScript="bengali" /> <Element latin:elementKeyboard="@xml/kbd_bengali_akkhor" latin:elementName="alphabet" diff --git a/java/res/xml/rows_number_normal.xml b/java/res/xml/rows_number_normal.xml index d8d15080e..0f92ac605 100644 --- a/java/res/xml/rows_number_normal.xml +++ b/java/res/xml/rows_number_normal.xml @@ -35,7 +35,8 @@ latin:keySpec="-" latin:moreKeys="+" latin:keyLabelFlags="hasPopupHint" - latin:keyStyle="numFunctionalKeyStyle" + latin:keyStyle="numKeyStyle" + latin:backgroundType="functional" latin:keyWidth="fillRight" /> </Row> <Row> @@ -54,7 +55,8 @@ > <Key latin:keySpec="." - latin:keyStyle="numFunctionalKeyStyle" + latin:keyStyle="numKeyStyle" + latin:backgroundType="functional" latin:keyWidth="fillRight" /> </case> <case @@ -62,15 +64,17 @@ > <Key latin:keySpec="." - latin:keyLabelFlags="hasPopupHint" latin:moreKeys="!text/morekeys_am_pm" - latin:keyStyle="numFunctionalKeyStyle" + latin:keyLabelFlags="hasPopupHint" + latin:keyStyle="numKeyStyle" + latin:backgroundType="functional" latin:keyWidth="fillRight" /> </case> <default> <Key latin:keySpec="," - latin:keyStyle="numFunctionalKeyStyle" + latin:keyStyle="numKeyStyle" + latin:backgroundType="functional" latin:keyWidth="fillRight" /> </default> </switch> diff --git a/java/res/xml/rows_number_password.xml b/java/res/xml/rows_number_password.xml index 2e61a08ae..65736c430 100644 --- a/java/res/xml/rows_number_password.xml +++ b/java/res/xml/rows_number_password.xml @@ -70,7 +70,8 @@ <Key latin:keyStyle="deleteKeyStyle" /> <Key - latin:keyStyle="num0KeyStyle" /> + latin:keyStyle="num0KeyStyle" + latin:keyHintLabel="+" /> <Key latin:keyStyle="enterKeyStyle" /> <!-- Note: This Spacer prevents the above key from being marked as a right edge key. --> diff --git a/java/res/xml/rows_phone.xml b/java/res/xml/rows_phone.xml index 03e45419a..bb5590d4e 100644 --- a/java/res/xml/rows_phone.xml +++ b/java/res/xml/rows_phone.xml @@ -36,7 +36,8 @@ latin:keySpec="-" latin:moreKeys="+" latin:keyLabelFlags="hasPopupHint" - latin:keyStyle="numFunctionalKeyStyle" + latin:keyStyle="numKeyStyle" + latin:backgroundType="functional" latin:keyWidth="fillRight" /> </Row> <Row> @@ -48,7 +49,8 @@ latin:keyStyle="num6KeyStyle" /> <Key latin:keySpec="." - latin:keyStyle="numFunctionalKeyStyle" + latin:keyStyle="numKeyStyle" + latin:backgroundType="functional" latin:keyWidth="fillRight" /> </Row> <Row> diff --git a/java/res/xml/rows_phone_symbols.xml b/java/res/xml/rows_phone_symbols.xml index 983bfb5c8..195a183a3 100644 --- a/java/res/xml/rows_phone_symbols.xml +++ b/java/res/xml/rows_phone_symbols.xml @@ -28,45 +28,53 @@ <Row> <Key latin:keySpec="(" - latin:keyStyle="numKeyStyle" /> + latin:keyStyle="numSymbolKeyStyle" /> <Key latin:keySpec="/" - latin:keyStyle="numKeyStyle" /> + latin:keyStyle="numSymbolKeyStyle" /> <Key latin:keySpec=")" - latin:keyStyle="numKeyStyle" /> + latin:keyStyle="numSymbolKeyStyle" /> <Key latin:keySpec="-" latin:moreKeys="+" latin:keyLabelFlags="hasPopupHint" - latin:keyStyle="numFunctionalKeyStyle" + latin:keyStyle="numKeyStyle" + latin:backgroundType="functional" latin:keyWidth="fillRight" /> </Row> <Row> <Key latin:keySpec="N" - latin:keyStyle="numKeyBaseStyle" /> + latin:keyStyle="numSymbolKeyStyle" /> <!-- Pause is a comma. Check PhoneNumberUtils.java to see if this has changed. --> <Key - latin:keyStyle="numPauseKeyStyle" /> + latin:keySpec="!string/label_pause_key|," + latin:keyLabelFlags="followKeyLabelRatio|autoXScale" + latin:keyStyle="numSymbolKeyStyle" /> <Key latin:keySpec="," - latin:keyStyle="numKeyStyle" /> + latin:keyStyle="numSymbolKeyStyle" /> <Key latin:keySpec="." - latin:keyStyle="numFunctionalKeyStyle" + latin:keyStyle="numKeyStyle" + latin:backgroundType="functional" latin:keyWidth="fillRight" /> </Row> <Row> + <!-- U+FF0A: "*" FULLWIDTH ASTERISK --> <Key - latin:keyStyle="numStarKeyStyle" /> + latin:keySpec="*|*" + latin:keyStyle="numSymbolKeyStyle" /> <!-- Wait is a semicolon. --> <Key - latin:keyStyle="numWaitKeyStyle" /> + latin:keySpec="!string/label_wait_key|;" + latin:keyLabelFlags="followKeyLabelRatio|autoXScale" + latin:keyStyle="numSymbolKeyStyle" /> <Key latin:keySpec="\#" - latin:keyStyle="numKeyStyle" /> + latin:keyStyle="numSymbolKeyStyle" /> <Key latin:keyStyle="deleteKeyStyle" latin:keyWidth="fillRight" /> @@ -76,7 +84,7 @@ latin:keyStyle="numPhoneToNumericKeyStyle" /> <Key latin:keySpec="+" - latin:keyStyle="numKeyStyle" /> + latin:keyStyle="numSymbolKeyStyle" /> <Key latin:keyStyle="numSpaceKeyStyle" /> <Key diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java index 7fc1e9d8a..2de71cec9 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java +++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java @@ -31,9 +31,9 @@ import android.view.inputmethod.EditorInfo; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.latin.common.CoordinateUtils; import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.settings.SettingsValues; -import com.android.inputmethod.latin.utils.CoordinateUtils; import java.util.List; diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java index 3a27c5739..58ad4bd4c 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java @@ -26,6 +26,8 @@ import com.android.inputmethod.latin.common.Constants; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import javax.annotation.Nonnull; + public final class InputMethodSubtypeCompatUtils { private static final String TAG = InputMethodSubtypeCompatUtils.class.getSimpleName(); // Note that InputMethodSubtype(int nameId, int iconId, String locale, String mode, @@ -53,6 +55,7 @@ public final class InputMethodSubtypeCompatUtils { } @SuppressWarnings("deprecation") + @Nonnull public static InputMethodSubtype newInputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) { diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java index d3e24e342..be0744393 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java @@ -27,8 +27,8 @@ import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.utils.LocaleUtils; import java.lang.reflect.Field; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java index 37fa76be7..659fe5c51 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java @@ -31,6 +31,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.utils.DebugLogUtils; import java.io.File; diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java index e9b634eec..e6acb8f36 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java @@ -25,6 +25,7 @@ import android.os.IBinder; import android.widget.Toast; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.common.LocaleUtils; import java.util.Locale; import java.util.Random; diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java index c2dc87900..14e005007 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java @@ -16,6 +16,8 @@ package com.android.inputmethod.dictionarypack; +import com.android.inputmethod.latin.common.LocaleUtils; + import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ContentResolver; diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java index f1633ff28..50b3c72f3 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java +++ b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java @@ -26,6 +26,7 @@ import android.widget.TextView; import com.android.inputmethod.annotations.ExternallyReferenced; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.common.LocaleUtils; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java index db4315f8f..ce23eb752 100644 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java +++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java @@ -47,7 +47,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper { // used to identify the versions for upgrades. This should never change going forward. private static final int METADATA_DATABASE_VERSION_WITH_CLIENTID = 6; // The current database version. - private static final int CURRENT_METADATA_DATABASE_VERSION = 10; + // This MUST be increased every time the dictionary pack metadata URL changes. + private static final int CURRENT_METADATA_DATABASE_VERSION = 11; private final static long NOT_A_DOWNLOAD_ID = -1; diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java index d59b7a545..aeb666704 100644 --- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java +++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java @@ -39,6 +39,8 @@ import com.android.inputmethod.compat.ConnectivityManagerCompatUtils; import com.android.inputmethod.compat.DownloadManagerCompatUtils; import com.android.inputmethod.compat.NotificationCompatUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.common.LocaleUtils; +import com.android.inputmethod.latin.makedict.FormatSpec; import com.android.inputmethod.latin.utils.ApplicationUtils; import com.android.inputmethod.latin.utils.DebugLogUtils; @@ -78,7 +80,8 @@ public final class UpdateHandler { // DownloadManager uses as an ID numbers returned out of an AUTOINCREMENT column // in SQLite, so it should never return anything < 0. public static final int NOT_AN_ID = -1; - public static final int MAXIMUM_SUPPORTED_FORMAT_VERSION = 2; + public static final int MAXIMUM_SUPPORTED_FORMAT_VERSION = + FormatSpec.MAXIMUM_SUPPORTED_STATIC_VERSION; // Arbitrary. Probably good if it's a power of 2, and a couple thousand bytes long. private static final int FILE_COPY_BUFFER_SIZE = 8192; diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index 3c90a04db..619b801f4 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -22,7 +22,7 @@ import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.common.CoordinateUtils; import java.util.ArrayList; import java.util.Collections; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index b674359e6..b1051385d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -38,7 +38,6 @@ import com.android.inputmethod.keyboard.internal.KeysCache; import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodSubtype; -import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.define.DebugFlags; import com.android.inputmethod.latin.utils.InputTypeUtils; import com.android.inputmethod.latin.utils.ScriptUtils; @@ -52,6 +51,9 @@ import java.io.IOException; import java.lang.ref.SoftReference; import java.util.HashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + /** * This class represents a set of keyboard layouts. Each of them represents a different keyboard * specific to a keyboard state, such as alphabet, symbols, and so on. Layouts in the same @@ -83,6 +85,8 @@ public final class KeyboardLayoutSet { private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache = new HashMap<>(); private static final KeysCache sKeysCache = new KeysCache(); + private final static HashMap<InputMethodSubtype, Integer> sScriptIdsForSubtypes = + new HashMap<>(); @SuppressWarnings("serial") public static final class KeyboardLayoutSetException extends RuntimeException { @@ -141,6 +145,16 @@ public final class KeyboardLayoutSet { sKeysCache.clear(); } + public static int getScriptId(final Resources resources, final InputMethodSubtype subtype) { + final Integer value = sScriptIdsForSubtypes.get(subtype); + if (null == value) { + final int scriptId = Builder.readScriptId(resources, subtype); + sScriptIdsForSubtypes.put(subtype, scriptId); + return scriptId; + } + return value; + } + KeyboardLayoutSet(final Context context, final Params params) { mContext = context; mParams = params; @@ -245,7 +259,7 @@ public final class KeyboardLayoutSet { private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo(); - public Builder(final Context context, final EditorInfo ei) { + public Builder(final Context context, @Nullable final EditorInfo ei) { mContext = context; mPackageName = context.getPackageName(); mResources = context.getResources(); @@ -266,7 +280,7 @@ public final class KeyboardLayoutSet { return this; } - public Builder setSubtype(final RichInputMethodSubtype subtype) { + public Builder setSubtype(@Nonnull final RichInputMethodSubtype subtype) { final boolean asciiCapable = InputMethodSubtypeCompatUtils.isAsciiCapable(subtype); // TODO: Consolidate with {@link InputAttributes}. @SuppressWarnings("deprecation") @@ -276,11 +290,11 @@ public final class KeyboardLayoutSet { mParams.mEditorInfo.imeOptions) || deprecatedForceAscii; final RichInputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable) - ? SubtypeSwitcher.getInstance().getNoLanguageSubtype() + ? RichInputMethodSubtype.getNoLanguageSubtype() : subtype; mParams.mSubtype = keyboardSubtype; mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX - + SubtypeLocaleUtils.getKeyboardLayoutSetName(keyboardSubtype); + + keyboardSubtype.getKeyboardLayoutSetName(); return this; } @@ -304,31 +318,13 @@ public final class KeyboardLayoutSet { return this; } - public Builder setScriptId(final int scriptId) { - mParams.mScriptId = scriptId; - return this; - } - public Builder setSplitLayoutEnabledByUser(final boolean enabled) { mParams.mIsSplitLayoutEnabledByUser = enabled; return this; } - private final static HashMap<InputMethodSubtype, Integer> sScriptIdsForSubtypes = - new HashMap<>(); - public static int getScriptId(final Resources resources, final InputMethodSubtype subtype) { - final Integer value = sScriptIdsForSubtypes.get(subtype); - if (null == value) { - final int scriptId = readScriptId(resources, subtype); - sScriptIdsForSubtypes.put(subtype, scriptId); - return scriptId; - } - return value; - } - // Super redux version of reading the script ID for some subtype from Xml. - private static int readScriptId(final Resources resources, - final InputMethodSubtype subtype) { + static int readScriptId(final Resources resources, final InputMethodSubtype subtype) { final String layoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); final int xmlId = getXmlId(resources, layoutSetName); @@ -359,7 +355,7 @@ public final class KeyboardLayoutSet { try { final int scriptId = featureAttr.getInt(R.styleable.KeyboardLayoutSet_Feature_supportedScript, - ScriptUtils.SCRIPT_UNKNOWN); + ScriptUtils.SCRIPT_UNKNOWN); XmlParseUtils.checkEndTag(TAG_FEATURE, parser); return scriptId; } finally { @@ -415,7 +411,7 @@ public final class KeyboardLayoutSet { if (TAG_ELEMENT.equals(tag)) { parseKeyboardLayoutSetElement(parser); } else if (TAG_FEATURE.equals(tag)) { - parseKeyboardLayoutSetFeature(parser); + mParams.mScriptId = readScriptIdFromTagFeature(mResources, parser); } else { throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD_SET); } @@ -460,12 +456,6 @@ public final class KeyboardLayoutSet { } } - private void parseKeyboardLayoutSetFeature(final XmlPullParser parser) - throws XmlPullParserException, IOException { - final int scriptId = readScriptIdFromTagFeature(mResources, parser); - setScriptId(scriptId); - } - private static int getKeyboardMode(final EditorInfo editorInfo) { final int inputType = editorInfo.inputType; final int variation = inputType & InputType.TYPE_MASK_VARIATION; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 89acc3cd3..5e3a5f17c 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -39,6 +39,8 @@ import com.android.inputmethod.latin.WordComposer; import com.android.inputmethod.latin.define.ProductionFlags; import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.settings.SettingsValues; +import com.android.inputmethod.latin.utils.CapsModeUtils; +import com.android.inputmethod.latin.utils.RecapitalizeStatus; import com.android.inputmethod.latin.utils.ResourceUtils; import com.android.inputmethod.latin.utils.ScriptUtils; @@ -112,7 +114,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res); final int keyboardHeight = ResourceUtils.getKeyboardHeight(res, settingsValues); builder.setKeyboardGeometry(keyboardWidth, keyboardHeight); - builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype()); + builder.setSubtype(RichInputMethodManager.getInstance().getCurrentSubtype()); builder.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey); builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey()); builder.setSplitLayoutEnabledByUser(ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED @@ -121,7 +123,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { try { mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState); // TODO: revisit this for multi-lingual input - mKeyboardTextsSet.setLocale(mSubtypeSwitcher.getCurrentSubtypeLocales()[0], + mKeyboardTextsSet.setLocale( + RichInputMethodManager.getInstance().getCurrentSubtypeLocales()[0], mThemeContext); } catch (KeyboardLayoutSetException e) { Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); @@ -161,7 +164,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { currentSettingsValues.mKeyPreviewDismissEndXScale, currentSettingsValues.mKeyPreviewDismissEndYScale, currentSettingsValues.mKeyPreviewDismissDuration); - keyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); + keyboardView.updateShortcutKey(RichInputMethodManager.getInstance().isShortcutImeReady()); final boolean subtypeChanged = (oldKeyboard == null) || !keyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype); final int languageOnSpacebarFormatType = mSubtypeSwitcher.getLanguageOnSpacebarFormatType( @@ -204,42 +207,64 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetKeyboard() { + if (DEBUG_ACTION) { + Log.d(TAG, "setAlphabetKeyboard"); + } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetManualShiftedKeyboard() { + if (DEBUG_ACTION) { + Log.d(TAG, "setAlphabetManualShiftedKeyboard"); + } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetAutomaticShiftedKeyboard() { + if (DEBUG_ACTION) { + Log.d(TAG, "setAlphabetAutomaticShiftedKeyboard"); + } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetShiftLockedKeyboard() { + if (DEBUG_ACTION) { + Log.d(TAG, "setAlphabetShiftLockedKeyboard"); + } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setAlphabetShiftLockShiftedKeyboard() { + if (DEBUG_ACTION) { + Log.d(TAG, "setAlphabetShiftLockShiftedKeyboard"); + } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setSymbolsKeyboard() { + if (DEBUG_ACTION) { + Log.d(TAG, "setSymbolsKeyboard"); + } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS)); } private void setMainKeyboardFrame(final SettingsValues settingsValues) { - mMainKeyboardFrame.setVisibility( - settingsValues.mHasHardwareKeyboard ? View.GONE : View.VISIBLE); + final int visibility = settingsValues.mHasHardwareKeyboard ? View.GONE : View.VISIBLE; + mKeyboardView.setVisibility(visibility); + // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}. + // @see #getVisibleKeyboardView() and + // @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets) + mMainKeyboardFrame.setVisibility(visibility); mEmojiPalettesView.setVisibility(View.GONE); mEmojiPalettesView.stopEmojiPalettes(); } @@ -247,8 +272,15 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void setEmojiKeyboard() { + if (DEBUG_ACTION) { + Log.d(TAG, "setEmojiKeyboard"); + } final Keyboard keyboard = mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); mMainKeyboardFrame.setVisibility(View.GONE); + // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}. + // @see #getVisibleKeyboardView() and + // @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets) + mKeyboardView.setVisibility(View.GONE); mEmojiPalettesView.startEmojiPalettes( mKeyboardTextsSet.getText(KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL), mKeyboardView.getKeyVisualAttribute(), keyboard.mIconsSet); @@ -269,19 +301,29 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void setSymbolsShiftedKeyboard() { + if (DEBUG_ACTION) { + Log.d(TAG, "setSymbolsShiftedKeyboard"); + } setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)); } // Future method for requesting an updating to the shift state. @Override - public void requestUpdatingShiftState(final int currentAutoCapsState, - final int currentRecapitalizeState) { - mState.onUpdateShiftState(currentAutoCapsState, currentRecapitalizeState); + public void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode) { + if (DEBUG_ACTION) { + Log.d(TAG, "requestUpdatingShiftState: " + + " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags) + + " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode)); + } + mState.onUpdateShiftState(autoCapsFlags, recapitalizeMode); } // Implements {@link KeyboardState.SwitchActions}. @Override public void startDoubleTapShiftKeyTimer() { + if (DEBUG_TIMER_ACTION) { + Log.d(TAG, "startDoubleTapShiftKeyTimer"); + } final MainKeyboardView keyboardView = getMainKeyboardView(); if (keyboardView != null) { keyboardView.startDoubleTapShiftKeyTimer(); @@ -291,6 +333,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void cancelDoubleTapShiftKeyTimer() { + if (DEBUG_TIMER_ACTION) { + Log.d(TAG, "setAlphabetKeyboard"); + } final MainKeyboardView keyboardView = getMainKeyboardView(); if (keyboardView != null) { keyboardView.cancelDoubleTapShiftKeyTimer(); @@ -300,6 +345,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public boolean isInDoubleTapShiftKeyTimeout() { + if (DEBUG_TIMER_ACTION) { + Log.d(TAG, "isInDoubleTapShiftKeyTimeout"); + } final MainKeyboardView keyboardView = getMainKeyboardView(); return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout(); } @@ -367,9 +415,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } public void onNetworkStateChanged() { - if (mKeyboardView != null) { - mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); + if (mKeyboardView == null) { + return; } + mKeyboardView.updateShortcutKey(RichInputMethodManager.getInstance().isShortcutImeReady()); } public int getKeyboardShiftMode() { diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index f23db04f3..cba7ff2a2 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -39,8 +39,8 @@ import android.view.ViewGroup; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate; import com.android.inputmethod.annotations.ExternallyReferenced; -import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView; +import com.android.inputmethod.keyboard.internal.DrawingProxy; import com.android.inputmethod.keyboard.internal.GestureFloatingTextDrawingPreview; import com.android.inputmethod.keyboard.internal.GestureTrailsDrawingPreview; import com.android.inputmethod.keyboard.internal.KeyDrawParams; @@ -56,9 +56,9 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodSubtype; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.CoordinateUtils; import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.settings.DebugSettings; -import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.TypefaceUtils; import java.util.Locale; @@ -110,7 +110,7 @@ import javax.annotation.Nullable; * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration */ -public final class MainKeyboardView extends KeyboardView implements PointerTracker.DrawingProxy, +public final class MainKeyboardView extends KeyboardView implements DrawingProxy, MoreKeysPanel.Controller { private static final String TAG = MainKeyboardView.class.getSimpleName(); diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java index 01522536f..3acc11b59 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java @@ -31,7 +31,7 @@ import com.android.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelega import com.android.inputmethod.keyboard.internal.KeyDrawParams; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.common.CoordinateUtils; /** * A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 467f5150a..7902ce852 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -25,18 +25,20 @@ import android.view.MotionEvent; import com.android.inputmethod.keyboard.internal.BatchInputArbiter; import com.android.inputmethod.keyboard.internal.BatchInputArbiter.BatchInputArbiterListener; import com.android.inputmethod.keyboard.internal.BogusMoveEventDetector; +import com.android.inputmethod.keyboard.internal.DrawingProxy; import com.android.inputmethod.keyboard.internal.GestureEnabler; import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingParams; import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingPoints; import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; +import com.android.inputmethod.keyboard.internal.TimerProxy; import com.android.inputmethod.keyboard.internal.TypingTimeRecorder; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.CoordinateUtils; import com.android.inputmethod.latin.common.InputPointers; import com.android.inputmethod.latin.define.DebugFlags; import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.ResourceUtils; import java.util.ArrayList; @@ -52,66 +54,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element, private static final boolean DEBUG_LISTENER = false; private static boolean DEBUG_MODE = DebugFlags.DEBUG_ENABLED || DEBUG_EVENT; - public interface DrawingProxy { - public void invalidateKey(@Nullable Key key); - public void showKeyPreview(@Nonnull Key key); - public void dismissKeyPreview(@Nonnull Key key); - public void dismissKeyPreviewWithoutDelay(@Nonnull Key key); - public void onLongPress(@Nonnull PointerTracker tracker); - public static final int FADE_IN = 0; - public static final int FADE_OUT = 1; - public void startWhileTypingAnimation(final int fadeInOrOut); - public void showSlidingKeyInputPreview(@Nullable PointerTracker tracker); - public void showGestureTrail(@Nonnull PointerTracker tracker, - boolean showsFloatingPreviewText); - public void dismissGestureFloatingPreviewTextWithoutDelay(); - } - - public interface TimerProxy { - public void startTypingStateTimer(Key typedKey); - public boolean isTypingState(); - public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay); - public void startLongPressTimerOf(PointerTracker tracker, int delay); - public void cancelLongPressTimerOf(PointerTracker tracker); - public void cancelLongPressShiftKeyTimers(); - public void cancelKeyTimersOf(PointerTracker tracker); - public void startDoubleTapShiftKeyTimer(); - public void cancelDoubleTapShiftKeyTimer(); - public boolean isInDoubleTapShiftKeyTimeout(); - public void startUpdateBatchInputTimer(PointerTracker tracker); - public void cancelUpdateBatchInputTimer(PointerTracker tracker); - public void cancelAllUpdateBatchInputTimers(); - - public static class Adapter implements TimerProxy { - @Override - public void startTypingStateTimer(Key typedKey) {} - @Override - public boolean isTypingState() { return false; } - @Override - public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay) {} - @Override - public void startLongPressTimerOf(PointerTracker tracker, int delay) {} - @Override - public void cancelLongPressTimerOf(PointerTracker tracker) {} - @Override - public void cancelLongPressShiftKeyTimers() {} - @Override - public void cancelKeyTimersOf(PointerTracker tracker) {} - @Override - public void startDoubleTapShiftKeyTimer() {} - @Override - public void cancelDoubleTapShiftKeyTimer() {} - @Override - public boolean isInDoubleTapShiftKeyTimeout() { return false; } - @Override - public void startUpdateBatchInputTimer(PointerTracker tracker) {} - @Override - public void cancelUpdateBatchInputTimer(PointerTracker tracker) {} - @Override - public void cancelAllUpdateBatchInputTimers() {} - } - } - static final class PointerTrackerParams { public final boolean mKeySelectionByDraggingFinger; public final int mTouchNoiseThresholdTime; @@ -586,7 +528,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, } sListener.onStartBatchInput(); dismissAllMoreKeysPanels(); - sTimerProxy.cancelLongPressTimerOf(this); + sTimerProxy.cancelLongPressTimersOf(this); } private void showGestureTrail() { @@ -1094,7 +1036,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, } public void cancelLongPressTimer() { - sTimerProxy.cancelLongPressTimerOf(this); + sTimerProxy.cancelLongPressTimersOf(this); } public void onLongPressed() { @@ -1163,7 +1105,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, private void startLongPressTimer(final Key key) { // Note that we need to cancel all active long press shift key timers if any whenever we // start a new long press timer for both non-shift and shift keys. - sTimerProxy.cancelLongPressShiftKeyTimers(); + sTimerProxy.cancelLongPressShiftKeyTimer(); if (sInGesture) return; if (key == null) return; if (!key.isLongPressEnabled()) return; diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java index 54d3e3b88..09313f811 100644 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java @@ -148,7 +148,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements void callListenerOnPressKey(final Key pressedKey) { mPendingKeyDown = null; - pressedKey.onReleased(); + pressedKey.onPressed(); invalidateKey(pressedKey); mListener.onPressKey(pressedKey); } diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java index 06184f8d2..cf4dd3db3 100644 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java @@ -48,7 +48,7 @@ import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.RichInputMethodSubtype; import com.android.inputmethod.latin.common.Constants; import com.android.inputmethod.latin.utils.ResourceUtils; @@ -113,7 +113,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange context, null /* editorInfo */); final Resources res = context.getResources(); mEmojiLayoutParams = new EmojiLayoutParams(res); - builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype()); + builder.setSubtype(RichInputMethodSubtype.getEmojiSubtype()); builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res), mEmojiLayoutParams.mEmojiKeyboardHeight); final KeyboardLayoutSet layoutSet = builder.build(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java index a5d47adb3..9c0d7436b 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java @@ -24,7 +24,7 @@ import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; import android.widget.RelativeLayout; -import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.common.CoordinateUtils; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java new file mode 100644 index 000000000..7fc586a0f --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.PointerTracker; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface DrawingProxy { + // TODO: Remove this method. + public void invalidateKey(@Nullable Key key); + + // TODO: Rename this method to onKeyPressed. + public void showKeyPreview(@Nonnull Key key); + + // TODO: Rename this method to onKeyReleased. + public void dismissKeyPreview(@Nonnull Key key); + + /** + * Dismiss a key preview visual without delay. + * @param key the key whose preview visual should be dismissed. + */ + public void dismissKeyPreviewWithoutDelay(@Nonnull Key key); + + // TODO: Rename this method to onKeyLongPressed. + public void onLongPress(@Nonnull PointerTracker tracker); + + /** + * Start a while-typing-animation. + * @param fadeInOrOut {@link #FADE_IN} starts while-typing-fade-in animation. + * {@link #FADE_OUT} starts while-typing-fade-out animation. + */ + public void startWhileTypingAnimation(int fadeInOrOut); + public static final int FADE_IN = 0; + public static final int FADE_OUT = 1; + + /** + * Show sliding-key input preview. + * @param tracker the {@link PointerTracker} that is currently doing the sliding-key input. + * null to dismiss the sliding-key input preview. + */ + public void showSlidingKeyInputPreview(@Nullable PointerTracker tracker); + + /** + * Show gesture trails. + * @param tracker the {@link PointerTracker} whose gesture trail will be shown. + * @param showsFloatingPreviewText when true, a gesture floating preview text will be shown + * with this <code>tracker</code>'s trail. + */ + public void showGestureTrail(@Nonnull PointerTracker tracker, boolean showsFloatingPreviewText); + + /** + * Dismiss a gesture floating preview text without delay. + */ + public void dismissGestureFloatingPreviewTextWithoutDelay(); +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java index 330ec52f0..5443c2a8c 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java @@ -27,7 +27,7 @@ import android.text.TextUtils; import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.common.CoordinateUtils; import javax.annotation.Nonnull; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java index d3764877c..448f1b4b1 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java @@ -23,7 +23,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.common.CoordinateUtils; import com.android.inputmethod.latin.utils.ViewLayoutUtils; import java.util.ArrayDeque; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java index c739bf3e0..51f89c122 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java @@ -36,7 +36,6 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.common.Constants; import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.utils.ResourceUtils; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import com.android.inputmethod.latin.utils.XmlParseUtils; import com.android.inputmethod.latin.utils.XmlParseUtils.ParseException; @@ -648,7 +647,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> { try { final boolean keyboardLayoutSetMatched = matchString(caseAttr, R.styleable.Keyboard_Case_keyboardLayoutSet, - SubtypeLocaleUtils.getKeyboardLayoutSetName(id.mSubtype)); + id.mSubtype.getKeyboardLayoutSetName()); final boolean keyboardLayoutSetElementMatched = matchTypedValue(caseAttr, R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId, KeyboardId.elementIdToName(id.mElementId)); diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index cc28e7ac8..70e116709 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -21,6 +21,7 @@ import android.util.Log; import com.android.inputmethod.event.Event; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.utils.CapsModeUtils; import com.android.inputmethod.latin.utils.RecapitalizeStatus; /** @@ -38,9 +39,11 @@ import com.android.inputmethod.latin.utils.RecapitalizeStatus; public final class KeyboardState { private static final String TAG = KeyboardState.class.getSimpleName(); private static final boolean DEBUG_EVENT = false; - private static final boolean DEBUG_ACTION = false; + private static final boolean DEBUG_INTERNAL_ACTION = false; public interface SwitchActions { + public static final boolean DEBUG_ACTION = false; + public void setAlphabetKeyboard(); public void setAlphabetManualShiftedKeyboard(); public void setAlphabetAutomaticShiftedKeyboard(); @@ -53,8 +56,9 @@ public final class KeyboardState { /** * Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}. */ - public void requestUpdatingShiftState(final int currentAutoCapsState, - final int currentRecapitalizeState); + public void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode); + + public static final boolean DEBUG_TIMER_ACTION = false; public void startDoubleTapShiftKeyTimer(); public boolean isInDoubleTapShiftKeyTimeout(); @@ -119,10 +123,9 @@ public final class KeyboardState { mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; } - public void onLoadKeyboard(final int currentAutoCapsState, - final int currentRecapitalizeState) { + public void onLoadKeyboard(final int autoCapsFlags, final int recapitalizeMode) { if (DEBUG_EVENT) { - Log.d(TAG, "onLoadKeyboard: " + this); + Log.d(TAG, "onLoadKeyboard: " + stateToString(autoCapsFlags, recapitalizeMode)); } // Reset alphabet shift state. mAlphabetShiftState.setShiftLocked(false); @@ -130,7 +133,7 @@ public final class KeyboardState { mPrevSymbolsKeyboardWasShifted = false; mShiftKeyState.onRelease(); mSymbolKeyState.onRelease(); - onRestoreKeyboardState(currentAutoCapsState, currentRecapitalizeState); + onRestoreKeyboardState(autoCapsFlags, recapitalizeMode); } private static final int UNSHIFT = 0; @@ -156,14 +159,14 @@ public final class KeyboardState { } } - private void onRestoreKeyboardState(final int currentAutoCapsState, - final int currentRecapitalizeState) { + private void onRestoreKeyboardState(final int autoCapsFlags, final int recapitalizeMode) { final SavedKeyboardState state = mSavedKeyboardState; if (DEBUG_EVENT) { - Log.d(TAG, "onRestoreKeyboardState: saved=" + state + " " + this); + Log.d(TAG, "onRestoreKeyboardState: saved=" + state + + " " + stateToString(autoCapsFlags, recapitalizeMode)); } if (!state.mIsValid || state.mIsAlphabetMode) { - setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState); + setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); } else if (state.mIsEmojiMode) { setEmojiKeyboard(); } else { @@ -188,7 +191,7 @@ public final class KeyboardState { } private void setShifted(final int shiftMode) { - if (DEBUG_ACTION) { + if (DEBUG_INTERNAL_ACTION) { Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this); } if (!mIsAlphabetMode) return; @@ -227,7 +230,7 @@ public final class KeyboardState { } private void setShiftLocked(final boolean shiftLocked) { - if (DEBUG_ACTION) { + if (DEBUG_INTERNAL_ACTION) { Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this); } if (!mIsAlphabetMode) return; @@ -241,10 +244,10 @@ public final class KeyboardState { mAlphabetShiftState.setShiftLocked(shiftLocked); } - private void toggleAlphabetAndSymbols(final int currentAutoCapsState, - final int currentRecapitalizeState) { - if (DEBUG_ACTION) { - Log.d(TAG, "toggleAlphabetAndSymbols: " + this); + private void toggleAlphabetAndSymbols(final int autoCapsFlags, final int recapitalizeMode) { + if (DEBUG_INTERNAL_ACTION) { + Log.d(TAG, "toggleAlphabetAndSymbols: " + + stateToString(autoCapsFlags, recapitalizeMode)); } if (mIsAlphabetMode) { mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); @@ -256,7 +259,7 @@ public final class KeyboardState { mPrevSymbolsKeyboardWasShifted = false; } else { mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; - setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState); + setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); if (mPrevMainKeyboardWasShiftLocked) { setShiftLocked(true); } @@ -266,15 +269,15 @@ public final class KeyboardState { // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). - private void resetKeyboardStateToAlphabet(final int currentAutoCapsState, - final int currentRecapitalizeState) { - if (DEBUG_ACTION) { - Log.d(TAG, "resetKeyboardStateToAlphabet: " + this); + private void resetKeyboardStateToAlphabet(final int autoCapsFlags, final int recapitalizeMode) { + if (DEBUG_INTERNAL_ACTION) { + Log.d(TAG, "resetKeyboardStateToAlphabet: " + + stateToString(autoCapsFlags, recapitalizeMode)); } if (mIsAlphabetMode) return; mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; - setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState); + setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); if (mPrevMainKeyboardWasShiftLocked) { setShiftLocked(true); } @@ -289,10 +292,9 @@ public final class KeyboardState { } } - private void setAlphabetKeyboard(final int currentAutoCapsState, - final int currentRecapitalizeState) { - if (DEBUG_ACTION) { - Log.d(TAG, "setAlphabetKeyboard"); + private void setAlphabetKeyboard(final int autoCapsFlags, final int recapitalizeMode) { + if (DEBUG_INTERNAL_ACTION) { + Log.d(TAG, "setAlphabetKeyboard: " + stateToString(autoCapsFlags, recapitalizeMode)); } mSwitchActions.setAlphabetKeyboard(); @@ -301,11 +303,11 @@ public final class KeyboardState { mIsSymbolShifted = false; mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; mSwitchState = SWITCH_STATE_ALPHA; - mSwitchActions.requestUpdatingShiftState(currentAutoCapsState, currentRecapitalizeState); + mSwitchActions.requestUpdatingShiftState(autoCapsFlags, recapitalizeMode); } private void setSymbolsKeyboard() { - if (DEBUG_ACTION) { + if (DEBUG_INTERNAL_ACTION) { Log.d(TAG, "setSymbolsKeyboard"); } mSwitchActions.setSymbolsKeyboard(); @@ -318,7 +320,7 @@ public final class KeyboardState { } private void setSymbolsShiftedKeyboard() { - if (DEBUG_ACTION) { + if (DEBUG_INTERNAL_ACTION) { Log.d(TAG, "setSymbolsShiftedKeyboard"); } mSwitchActions.setSymbolsShiftedKeyboard(); @@ -331,7 +333,7 @@ public final class KeyboardState { } private void setEmojiKeyboard() { - if (DEBUG_ACTION) { + if (DEBUG_INTERNAL_ACTION) { Log.d(TAG, "setEmojiKeyboard"); } mIsAlphabetMode = false; @@ -343,11 +345,12 @@ public final class KeyboardState { mSwitchActions.setEmojiKeyboard(); } - public void onPressKey(final int code, final boolean isSinglePointer, - final int currentAutoCapsState, final int currentRecapitalizeState) { + public void onPressKey(final int code, final boolean isSinglePointer, final int autoCapsFlags, + final int recapitalizeMode) { if (DEBUG_EVENT) { - Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code) + " single=" - + isSinglePointer + " autoCaps=" + currentAutoCapsState + " " + this); + Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code) + + " single=" + isSinglePointer + + " " + stateToString(autoCapsFlags, recapitalizeMode)); } if (code != Constants.CODE_SHIFT) { // Because the double tap shift key timer is to detect two consecutive shift key press, @@ -359,7 +362,7 @@ public final class KeyboardState { } else if (code == Constants.CODE_CAPSLOCK) { // Nothing to do here. See {@link #onReleaseKey(int,boolean)}. } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { - onPressSymbol(currentAutoCapsState, currentRecapitalizeState); + onPressSymbol(autoCapsFlags, recapitalizeMode); } else { mShiftKeyState.onOtherKeyPressed(); mSymbolKeyState.onOtherKeyPressed(); @@ -372,7 +375,7 @@ public final class KeyboardState { // off because, for example, we may be in the #1 state within the manual temporary // shifted mode. if (!isSinglePointer && mIsAlphabetMode - && currentAutoCapsState != TextUtils.CAP_MODE_CHARACTERS) { + && autoCapsFlags != TextUtils.CAP_MODE_CHARACTERS) { final boolean needsToResetAutoCaps = mAlphabetShiftState.isAutomaticShifted() || (mAlphabetShiftState.isManualShifted() && mShiftKeyState.isReleasing()); if (needsToResetAutoCaps) { @@ -382,34 +385,35 @@ public final class KeyboardState { } } - public void onReleaseKey(final int code, final boolean withSliding, - final int currentAutoCapsState, final int currentRecapitalizeState) { + public void onReleaseKey(final int code, final boolean withSliding, final int autoCapsFlags, + final int recapitalizeMode) { if (DEBUG_EVENT) { Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code) - + " sliding=" + withSliding + " " + this); + + " sliding=" + withSliding + + " " + stateToString(autoCapsFlags, recapitalizeMode)); } if (code == Constants.CODE_SHIFT) { - onReleaseShift(withSliding, currentAutoCapsState, currentRecapitalizeState); + onReleaseShift(withSliding, autoCapsFlags, recapitalizeMode); } else if (code == Constants.CODE_CAPSLOCK) { setShiftLocked(!mAlphabetShiftState.isShiftLocked()); } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { - onReleaseSymbol(withSliding, currentAutoCapsState, currentRecapitalizeState); + onReleaseSymbol(withSliding, autoCapsFlags, recapitalizeMode); } } - private void onPressSymbol(final int currentAutoCapsState, - final int currentRecapitalizeState) { - toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState); + private void onPressSymbol(final int autoCapsFlags, + final int recapitalizeMode) { + toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); mSymbolKeyState.onPress(); mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL; } - private void onReleaseSymbol(final boolean withSliding, final int currentAutoCapsState, - final int currentRecapitalizeState) { + private void onReleaseSymbol(final boolean withSliding, final int autoCapsFlags, + final int recapitalizeMode) { if (mSymbolKeyState.isChording()) { // Switch back to the previous keyboard mode if the user chords the mode change key and // another key, then releases the mode change key. - toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState); + toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); } else if (!withSliding) { // If the mode change key is being released without sliding, we should forget the // previous symbols keyboard shift state and simply switch back to symbols layout @@ -419,23 +423,23 @@ public final class KeyboardState { mSymbolKeyState.onRelease(); } - public void onUpdateShiftState(final int autoCaps, final int recapitalizeMode) { + public void onUpdateShiftState(final int autoCapsFlags, final int recapitalizeMode) { if (DEBUG_EVENT) { - Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + ", recapitalizeMode=" - + recapitalizeMode + " " + this); + Log.d(TAG, "onUpdateShiftState: " + stateToString(autoCapsFlags, recapitalizeMode)); } mRecapitalizeMode = recapitalizeMode; - updateAlphabetShiftState(autoCaps, recapitalizeMode); + updateAlphabetShiftState(autoCapsFlags, recapitalizeMode); } // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). - public void onResetKeyboardStateToAlphabet(final int currentAutoCapsState, - final int currentRecapitalizeState) { + public void onResetKeyboardStateToAlphabet(final int autoCapsFlags, + final int recapitalizeMode) { if (DEBUG_EVENT) { - Log.d(TAG, "onResetKeyboardStateToAlphabet: " + this); + Log.d(TAG, "onResetKeyboardStateToAlphabet: " + + stateToString(autoCapsFlags, recapitalizeMode)); } - resetKeyboardStateToAlphabet(currentAutoCapsState, currentRecapitalizeState); + resetKeyboardStateToAlphabet(autoCapsFlags, recapitalizeMode); } private void updateShiftStateForRecapitalize(final int recapitalizeMode) { @@ -453,7 +457,7 @@ public final class KeyboardState { } } - private void updateAlphabetShiftState(final int autoCaps, final int recapitalizeMode) { + private void updateAlphabetShiftState(final int autoCapsFlags, final int recapitalizeMode) { if (!mIsAlphabetMode) return; if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != recapitalizeMode) { // We are recapitalizing. Match the keyboard to the current recapitalize state. @@ -466,7 +470,7 @@ public final class KeyboardState { return; } if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) { - if (mShiftKeyState.isReleasing() && autoCaps != Constants.TextUtils.CAP_MODE_OFF) { + if (mShiftKeyState.isReleasing() && autoCapsFlags != Constants.TextUtils.CAP_MODE_OFF) { // Only when shift key is releasing, automatic temporary upper case will be set. setShifted(AUTOMATIC_SHIFT); } else { @@ -526,8 +530,8 @@ public final class KeyboardState { } } - private void onReleaseShift(final boolean withSliding, final int currentAutoCapsState, - final int currentRecapitalizeState) { + private void onReleaseShift(final boolean withSliding, final int autoCapsFlags, + final int recapitalizeMode) { if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) { // We are recapitalizing. We should match the keyboard state to the recapitalize // state in priority. @@ -550,8 +554,7 @@ public final class KeyboardState { // After chording input, automatic shift state may have been changed depending on // what characters were input. mShiftKeyState.onRelease(); - mSwitchActions.requestUpdatingShiftState(currentAutoCapsState, - currentRecapitalizeState); + mSwitchActions.requestUpdatingShiftState(autoCapsFlags, recapitalizeMode); return; } else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) { // In shift locked state, shift has been pressed and slid out to other key. @@ -588,21 +591,20 @@ public final class KeyboardState { mShiftKeyState.onRelease(); } - public void onFinishSlidingInput(final int currentAutoCapsState, - final int currentRecapitalizeState) { + public void onFinishSlidingInput(final int autoCapsFlags, final int recapitalizeMode) { if (DEBUG_EVENT) { - Log.d(TAG, "onFinishSlidingInput: " + this); + Log.d(TAG, "onFinishSlidingInput: " + stateToString(autoCapsFlags, recapitalizeMode)); } // Switch back to the previous keyboard mode if the user cancels sliding input. switch (mSwitchState) { case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: - toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState); + toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); break; case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: toggleShiftInSymbols(); break; case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: - setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState); + setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); break; } } @@ -611,12 +613,11 @@ public final class KeyboardState { return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER; } - public void onEvent(final Event event, final int currentAutoCapsState, - final int currentRecapitalizeState) { + public void onEvent(final Event event, final int autoCapsFlags, final int recapitalizeMode) { final int code = event.isFunctionalKeyEvent() ? event.mKeyCode : event.mCodePoint; if (DEBUG_EVENT) { Log.d(TAG, "onEvent: code=" + Constants.printableCode(code) - + " autoCaps=" + currentAutoCapsState + " " + this); + + " " + stateToString(autoCapsFlags, recapitalizeMode)); } switch (mSwitchState) { @@ -652,7 +653,7 @@ public final class KeyboardState { // Switch back to alpha keyboard mode if user types one or more non-space/enter // characters followed by a space/enter. if (isSpaceOrEnter(code)) { - toggleAlphabetAndSymbols(currentAutoCapsState, currentRecapitalizeState); + toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); mPrevSymbolsKeyboardWasShifted = false; } break; @@ -660,11 +661,11 @@ public final class KeyboardState { // If the code is a letter, update keyboard shift state. if (Constants.isLetterCode(code)) { - updateAlphabetShiftState(currentAutoCapsState, currentRecapitalizeState); + updateAlphabetShiftState(autoCapsFlags, recapitalizeMode); } else if (code == Constants.CODE_EMOJI) { setEmojiKeyboard(); } else if (code == Constants.CODE_ALPHA_FROM_EMOJI) { - setAlphabetKeyboard(currentAutoCapsState, currentRecapitalizeState); + setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); } } @@ -697,4 +698,9 @@ public final class KeyboardState { + " symbol=" + mSymbolKeyState + " switch=" + switchStateToString(mSwitchState) + "]"; } + + private String stateToString(final int autoCapsFlags, final int recapitalizeMode) { + return this + " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags) + + " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode); + } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java b/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java index 21eaed950..8ed80107a 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java +++ b/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java @@ -25,6 +25,8 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import javax.annotation.Nonnull; + /** * This class determines that the language name on the spacebar should be displayed in what format. */ @@ -37,7 +39,7 @@ public final class LanguageOnSpacebarHelper { private List<InputMethodSubtype> mEnabledSubtypes = Collections.emptyList(); private boolean mIsSystemLanguageSameAsInputLanguage; - public int getLanguageOnSpacebarFormatType(final RichInputMethodSubtype subtype) { + public int getLanguageOnSpacebarFormatType(@Nonnull final RichInputMethodSubtype subtype) { if (subtype.isNoLanguage()) { return FORMAT_TYPE_FULL_LOCALE; } @@ -50,7 +52,7 @@ public final class LanguageOnSpacebarHelper { return FORMAT_TYPE_MULTIPLE; } final String keyboardLanguage = locales[0].getLanguage(); - final String keyboardLayout = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); + final String keyboardLayout = subtype.getKeyboardLayoutSetName(); int sameLanguageAndLayoutCount = 0; for (final InputMethodSubtype ims : mEnabledSubtypes) { final String language = SubtypeLocaleUtils.getSubtypeLocale(ims).getLanguage(); @@ -65,11 +67,30 @@ public final class LanguageOnSpacebarHelper { : FORMAT_TYPE_LANGUAGE_ONLY; } - public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) { + public void onUpdateEnabledSubtypes(@Nonnull final List<InputMethodSubtype> enabledSubtypes) { mEnabledSubtypes = enabledSubtypes; } - public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) { - mIsSystemLanguageSameAsInputLanguage = isSame; + public void onSubtypeChanged(@Nonnull final RichInputMethodSubtype subtype, + final boolean implicitlyEnabledSubtype, @Nonnull final Locale systemLocale) { + final Locale[] newLocales = subtype.getLocales(); + if (newLocales.length > 1) { + // In multi-locales mode, the system language is never the same as the input language + // because there is no single input language. + mIsSystemLanguageSameAsInputLanguage = false; + return; + } + final Locale newLocale = newLocales[0]; + if (systemLocale.equals(newLocale)) { + mIsSystemLanguageSameAsInputLanguage = true; + return; + } + if (!systemLocale.getLanguage().equals(newLocale.getLanguage())) { + mIsSystemLanguageSameAsInputLanguage = false; + return; + } + // If the subtype is enabled explicitly, the language name should be displayed even when + // the keyboard language and the system language are equal. + mIsSystemLanguageSameAsInputLanguage = implicitlyEnabledSubtype; } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java index a0bb406aa..b1a3887d8 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java @@ -21,10 +21,10 @@ import android.util.SparseIntArray; import com.android.inputmethod.compat.CharacterCompat; import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.latin.common.CollectionUtils; import com.android.inputmethod.latin.common.Constants; import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.Arrays; diff --git a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java index 3a9aa81a3..8a375c620 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java +++ b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java @@ -22,7 +22,7 @@ import android.view.MotionEvent; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.common.CoordinateUtils; public final class NonDistinctMultitouchHelper { private static final String TAG = NonDistinctMultitouchHelper.class.getSimpleName(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java index ef4c74d61..73a6f9516 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java +++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java @@ -23,7 +23,7 @@ import android.graphics.Path; import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.common.CoordinateUtils; /** * Draw rubber band preview graphics during sliding key input. diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java index 456522535..8068427bc 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java +++ b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java @@ -22,8 +22,6 @@ import android.view.ViewConfiguration; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; -import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.latin.common.Constants; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; @@ -86,7 +84,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> } @Override - public void startKeyRepeatTimerOf(final PointerTracker tracker, final int repeatCount, + public void startKeyRepeatTimerOf(@Nonnull final PointerTracker tracker, final int repeatCount, final int delay) { final Key key = tracker.getKey(); if (key == null || delay == 0) { @@ -110,7 +108,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> } @Override - public void startLongPressTimerOf(final PointerTracker tracker, final int delay) { + public void startLongPressTimerOf(@Nonnull final PointerTracker tracker, final int delay) { final Key key = tracker.getKey(); if (key == null) { return; @@ -123,13 +121,13 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> } @Override - public void cancelLongPressTimerOf(final PointerTracker tracker) { + public void cancelLongPressTimersOf(@Nonnull final PointerTracker tracker) { removeMessages(MSG_LONGPRESS_KEY, tracker); removeMessages(MSG_LONGPRESS_SHIFT_KEY, tracker); } @Override - public void cancelLongPressShiftKeyTimers() { + public void cancelLongPressShiftKeyTimer() { removeMessages(MSG_LONGPRESS_SHIFT_KEY); } @@ -139,7 +137,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> } @Override - public void startTypingStateTimer(final Key typedKey) { + public void startTypingStateTimer(@Nonnull final Key typedKey) { if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) { return; } @@ -190,9 +188,9 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> } @Override - public void cancelKeyTimersOf(final PointerTracker tracker) { + public void cancelKeyTimersOf(@Nonnull final PointerTracker tracker) { cancelKeyRepeatTimerOf(tracker); - cancelLongPressTimerOf(tracker); + cancelLongPressTimersOf(tracker); } public void cancelAllKeyTimers() { @@ -201,7 +199,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> } @Override - public void startUpdateBatchInputTimer(final PointerTracker tracker) { + public void startUpdateBatchInputTimer(@Nonnull final PointerTracker tracker) { if (mGestureRecognitionUpdateTime <= 0) { return; } @@ -211,7 +209,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> } @Override - public void cancelUpdateBatchInputTimer(final PointerTracker tracker) { + public void cancelUpdateBatchInputTimer(@Nonnull final PointerTracker tracker) { removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java b/java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java new file mode 100644 index 000000000..0ce3de8d9 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/TimerProxy.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.PointerTracker; + +import javax.annotation.Nonnull; + +public interface TimerProxy { + /** + * Start a timer to detect if a user is typing keys. + * @param typedKey the key that is typed. + */ + public void startTypingStateTimer(@Nonnull Key typedKey); + + /** + * Check if a user is key typing. + * @return true if a user is in typing. + */ + public boolean isTypingState(); + + /** + * Start a timer to simulate repeated key presses while a user keep pressing a key. + * @param tracker the {@link PointerTracker} that points the key to be repeated. + * @param repeatCount the number of times that the key is repeating. Starting from 1. + * @param delay the interval delay to the next key repeat, in millisecond. + */ + public void startKeyRepeatTimerOf(@Nonnull PointerTracker tracker, int repeatCount, int delay); + + /** + * Start a timer to detect a long pressed key. + * If a key pointed by <code>tracker</code> is a shift key, start another timer to detect + * long pressed shift key. + * @param tracker the {@link PointerTracker} that starts long pressing. + * @param delay the delay to fire the long press timer, in millisecond. + */ + public void startLongPressTimerOf(@Nonnull PointerTracker tracker, int delay); + + /** + * Cancel timers for detecting a long pressed key and a long press shift key. + * @param tracker cancel long press timers of this {@link PointerTracker}. + */ + public void cancelLongPressTimersOf(@Nonnull PointerTracker tracker); + + /** + * Cancel a timer for detecting a long pressed shift key. + */ + public void cancelLongPressShiftKeyTimer(); + + /** + * Cancel timers for detecting repeated key press, long pressed key, and long pressed shift key. + * @param tracker the {@link PointerTracker} that starts timers to be canceled. + */ + public void cancelKeyTimersOf(@Nonnull PointerTracker tracker); + + /** + * Start a timer to detect double tapped shift key. + */ + public void startDoubleTapShiftKeyTimer(); + + /** + * Cancel a timer of detecting double tapped shift key. + */ + public void cancelDoubleTapShiftKeyTimer(); + + /** + * Check if a timer of detecting double tapped shift key is running. + * @return true if detecting double tapped shift key is on going. + */ + public boolean isInDoubleTapShiftKeyTimeout(); + + /** + * Start a timer to fire updating batch input while <code>tracker</code> is on hold. + * @param tracker the {@link PointerTracker} that stops moving. + */ + public void startUpdateBatchInputTimer(@Nonnull PointerTracker tracker); + + /** + * Cancel a timer of firing updating batch input. + * @param tracker the {@link PointerTracker} that resumes moving or ends gesture input. + */ + public void cancelUpdateBatchInputTimer(@Nonnull PointerTracker tracker); + + /** + * Cancel all timers of firing updating batch input. + */ + public void cancelAllUpdateBatchInputTimers(); + + public static class Adapter implements TimerProxy { + @Override + public void startTypingStateTimer(@Nonnull Key typedKey) {} + @Override + public boolean isTypingState() { return false; } + @Override + public void startKeyRepeatTimerOf(@Nonnull PointerTracker tracker, int repeatCount, + int delay) {} + @Override + public void startLongPressTimerOf(@Nonnull PointerTracker tracker, int delay) {} + @Override + public void cancelLongPressTimersOf(@Nonnull PointerTracker tracker) {} + @Override + public void cancelLongPressShiftKeyTimer() {} + @Override + public void cancelKeyTimersOf(@Nonnull PointerTracker tracker) {} + @Override + public void startDoubleTapShiftKeyTimer() {} + @Override + public void cancelDoubleTapShiftKeyTimer() {} + @Override + public boolean isInDoubleTapShiftKeyTimeout() { return false; } + @Override + public void startUpdateBatchInputTimer(@Nonnull PointerTracker tracker) {} + @Override + public void cancelUpdateBatchInputTimer(@Nonnull PointerTracker tracker) {} + @Override + public void cancelAllUpdateBatchInputTimers() {} + } +} diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java index fd6c24dfe..f8d02d6ea 100644 --- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java +++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java @@ -16,7 +16,7 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.latin.utils.FileUtils; +import com.android.inputmethod.latin.common.FileUtils; import java.io.File; @@ -62,4 +62,9 @@ public final class AssetFileAddress { public void deleteUnderlyingFile() { FileUtils.deleteRecursively(new File(mFilename)); } + + @Override + public String toString() { + return String.format("%s (offset=%d, length=%d)", mFilename, mOffset, mLength); + } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 8e9b5c6f6..46cd3b8b2 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -21,9 +21,10 @@ import android.util.Log; import android.util.SparseArray; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.common.ComposedData; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.FileUtils; import com.android.inputmethod.latin.common.InputPointers; import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.makedict.DictionaryHeader; @@ -33,7 +34,6 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.WordProperty; import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.WordInputEventForPersonalization; @@ -262,8 +262,8 @@ public final class BinaryDictionary extends Dictionary { } @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final NgramContext ngramContext, final ProximityInfo proximityInfo, + public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, + final NgramContext ngramContext, final long proximityInfoHandle, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId, final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel) { @@ -274,12 +274,13 @@ public final class BinaryDictionary extends Dictionary { Arrays.fill(session.mInputCodePoints, Constants.NOT_A_CODE); ngramContext.outputToArray(session.mPrevWordCodePointArrays, session.mIsBeginningOfSentenceArray); - final InputPointers inputPointers = composer.getInputPointers(); - final boolean isGesture = composer.isBatchMode(); + final InputPointers inputPointers = composedData.mInputPointers; + final boolean isGesture = composedData.mIsBatchMode; final int inputSize; if (!isGesture) { - inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount( - session.mInputCodePoints); + inputSize = + composedData.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount( + session.mInputCodePoints); if (inputSize < 0) { return null; } @@ -303,7 +304,7 @@ public final class BinaryDictionary extends Dictionary { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL; } // TOOD: Pass multiple previous words information for n-gram. - getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(), + getSuggestionsNative(mNativeDict, proximityInfoHandle, getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(), inputPointers.getYCoordinates(), inputPointers.getTimes(), inputPointers.getPointerIds(), session.mInputCodePoints, inputSize, diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 1570bdae0..5afb62b69 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -21,11 +21,11 @@ import android.content.SharedPreferences; import android.content.res.AssetFileDescriptor; import android.util.Log; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; import com.android.inputmethod.latin.utils.DictionaryInfoUtils; -import com.android.inputmethod.latin.utils.LocaleUtils; import java.io.File; import java.io.IOException; @@ -284,7 +284,8 @@ final public class BinaryDictionaryGetter { final AssetFileAddress afa = AssetFileAddress.makeFromFileName(f.getPath()); if (null != afa) fileList.add(afa); } else { - Log.e(TAG, "Found a cached dictionary file but cannot read or use it"); + Log.e(TAG, "Found a cached dictionary file for " + locale.toString() + + " but cannot read or use it"); } } diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java index 95390aa9f..aefefd305 100644 --- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java +++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java @@ -17,7 +17,8 @@ package com.android.inputmethod.latin; import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.settings.NativeSuggestOptions; +import com.android.inputmethod.latin.common.NativeSuggestOptions; +import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils; import com.android.inputmethod.latin.utils.JniUtils; import java.util.Locale; @@ -43,7 +44,8 @@ public final class DicTraverseSession { public final int[] mOutputAutoCommitFirstWordConfidence = new int[1]; public final float[] mInputOutputWeightOfLangModelVsSpatialModel = new float[1]; - public final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions(); + public final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions( + AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE); private static native long setDicTraverseSessionNative(String locale, long dictSize); private static native void initDicTraverseSessionNative(long nativeDicTraverseSession, diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 28a62b283..7d7ed77e7 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -17,8 +17,8 @@ package com.android.inputmethod.latin; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.common.ComposedData; import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; import java.util.ArrayList; @@ -87,9 +87,9 @@ public abstract class Dictionary { /** * Searches for suggestions for a given context. - * @param composer the key sequence to match with coordinate info, as a WordComposer + * @param composedData the key sequence to match with coordinate info * @param ngramContext the context for n-gram. - * @param proximityInfo the object for key proximity. May be ignored by some implementations. + * @param proximityInfoHandle the handle for key proximity. Is ignored by some implementations. * @param settingsValuesForSuggestion the settings values used for the suggestion. * @param sessionId the session id. * @param weightForLocale the weight given to this locale, to multiply the output scores for @@ -99,8 +99,8 @@ public abstract class Dictionary { * a float array that has only one element. This can be updated when a different value is used. * @return the list of suggestions (possibly null if none) */ - abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final NgramContext ngramContext, final ProximityInfo proximityInfo, + abstract public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, + final NgramContext ngramContext, final long proximityInfoHandle, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId, final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel); @@ -203,8 +203,8 @@ public abstract class Dictionary { } @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final NgramContext ngramContext, final ProximityInfo proximityInfo, + public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, + final NgramContext ngramContext, final long proximityInfoHandle, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId, final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel) { diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index a6d7205e2..96575f629 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -18,8 +18,8 @@ package com.android.inputmethod.latin; import android.util.Log; -import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.common.ComposedData; import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; import java.util.ArrayList; @@ -59,8 +59,8 @@ public final class DictionaryCollection extends Dictionary { } @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final NgramContext ngramContext, final ProximityInfo proximityInfo, + public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, + final NgramContext ngramContext, final long proximityInfoHandle, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId, final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel) { @@ -68,15 +68,15 @@ public final class DictionaryCollection extends Dictionary { 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, - ngramContext, proximityInfo, settingsValuesForSuggestion, sessionId, + ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composedData, + ngramContext, proximityInfoHandle, settingsValuesForSuggestion, sessionId, weightForLocale, inOutWeightOfLangModelVsSpatialModel); if (null == suggestions) suggestions = new ArrayList<>(); final int length = dictionaries.size(); for (int i = 1; i < length; ++ i) { - final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer, - ngramContext, proximityInfo, settingsValuesForSuggestion, sessionId, - weightForLocale, inOutWeightOfLangModelVsSpatialModel); + final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions( + composedData, ngramContext, proximityInfoHandle, settingsValuesForSuggestion, + sessionId, weightForLocale, inOutWeightOfLangModelVsSpatialModel); if (null != sugg) suggestions.addAll(sugg); } return suggestions; diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index 4a22cde7b..d23639a0d 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -23,7 +23,6 @@ import android.util.Pair; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.ExpandableBinaryDictionary.UpdateEntriesForInputEventsCallback; import com.android.inputmethod.latin.NgramContext.WordInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; @@ -683,7 +682,7 @@ public class DictionaryFacilitator { // TODO: Revise the way to fusion suggestion results. public SuggestionResults getSuggestionResults(final WordComposer composer, - final NgramContext ngramContext, final ProximityInfo proximityInfo, + final NgramContext ngramContext, final long proximityInfoHandle, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) { final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; final SuggestionResults suggestionResults = new SuggestionResults( @@ -698,8 +697,8 @@ public class DictionaryFacilitator { ? dictionaryGroup.mWeightForGesturingInLocale : dictionaryGroup.mWeightForTypingInLocale; final ArrayList<SuggestedWordInfo> dictionarySuggestions = - dictionary.getSuggestions(composer, ngramContext, proximityInfo, - settingsValuesForSuggestion, sessionId, + dictionary.getSuggestions(composer.getComposedDataSnapshot(), ngramContext, + proximityInfoHandle, settingsValuesForSuggestion, sessionId, weightForLocale, weightOfLangModelVsSpatialModel); if (null == dictionarySuggestions) continue; suggestionResults.addAll(dictionarySuggestions); diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 702d1536a..d9d22e0fc 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -20,9 +20,10 @@ import android.content.Context; import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.common.ComposedData; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.FileUtils; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.makedict.FormatSpec; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; @@ -32,7 +33,6 @@ import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.CombinedFormatUtils; import com.android.inputmethod.latin.utils.DistracterFilter; import com.android.inputmethod.latin.utils.ExecutorUtils; -import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.WordInputEventForPersonalization; import java.io.File; @@ -120,7 +120,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private static boolean needsToMigrateDictionary(final int formatVersion) { // When we bump up the dictionary format version, the old version should be added to here // for supporting migration. Note that native code has to support reading such formats. - return formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING; + return formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING + || formatVersion == FormatSpec.VERSION402; } public boolean isValidDictionaryLocked() { @@ -480,8 +481,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final NgramContext ngramContext, final ProximityInfo proximityInfo, + public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, + final NgramContext ngramContext, final long proximityInfoHandle, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId, final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel) { reloadDictionaryIfRequired(); @@ -494,9 +495,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return null; } final ArrayList<SuggestedWordInfo> suggestions = - mBinaryDictionary.getSuggestions(composer, ngramContext, proximityInfo, - settingsValuesForSuggestion, sessionId, weightForLocale, - inOutWeightOfLangModelVsSpatialModel); + mBinaryDictionary.getSuggestions(composedData, ngramContext, + proximityInfoHandle, settingsValuesForSuggestion, sessionId, + weightForLocale, inOutWeightOfLangModelVsSpatialModel); if (mBinaryDictionary.isCorrupted()) { Log.i(TAG, "Dictionary (" + mDictName +") is corrupted. " + "Remove and regenerate it."); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 3fa127005..719656b07 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -77,6 +77,7 @@ import com.android.inputmethod.keyboard.TextDecoratorUi; import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.CoordinateUtils; import com.android.inputmethod.latin.common.InputPointers; import com.android.inputmethod.latin.define.DebugFlags; import com.android.inputmethod.latin.define.ProductionFlags; @@ -93,7 +94,6 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; import com.android.inputmethod.latin.touchinputconsumer.GestureConsumer; import com.android.inputmethod.latin.utils.ApplicationUtils; import com.android.inputmethod.latin.utils.CapsModeUtils; -import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.CursorAnchorInfoUtils; import com.android.inputmethod.latin.utils.DialogUtils; import com.android.inputmethod.latin.utils.ImportantNoticeUtils; @@ -603,7 +603,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Has to be package-visible for unit tests @UsedForTesting void loadSettings() { - final Locale[] locales = mSubtypeSwitcher.getCurrentSubtypeLocales(); + final Locale[] locales = mRichImm.getCurrentSubtypeLocales(); final EditorInfo editorInfo = getCurrentInputEditorInfo(); final InputAttributes inputAttributes = new InputAttributes( editorInfo, isFullscreenMode(), getPackageName()); @@ -626,7 +626,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void refreshPersonalizationDictionarySession( final SettingsValues currentSettingsValues) { mDictionaryFacilitator.setIsMonolingualUser( - mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes()); + mRichImm.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes()); mPersonalizationDictionaryUpdater.onLoadSettings( currentSettingsValues.mUsePersonalizedDicts); mContextualDictionaryUpdater.onLoadSettings(currentSettingsValues.mUsePersonalizedDicts); @@ -657,7 +657,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } void resetDictionaryFacilitatorIfNecessary() { - final Locale[] subtypeSwitcherLocales = mSubtypeSwitcher.getCurrentSubtypeLocales(); + final Locale[] subtypeSwitcherLocales = mRichImm.getCurrentSubtypeLocales(); if (mDictionaryFacilitator.isForLocales(subtypeSwitcherLocales)) { return; } @@ -863,7 +863,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // also wouldn't be consuming gesture data. mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; mRichImm.clearSubtypeCaches(); - mSubtypeSwitcher.refreshSubtypeInfo(); + mSubtypeSwitcher.onSubtypeChanged(mRichImm.getCurrentRawSubtype()); final KeyboardSwitcher switcher = mKeyboardSwitcher; switcher.updateKeyboardTheme(); final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView(); @@ -909,7 +909,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Update to a gesture consumer with the current editor and IME state. mGestureConsumer = GestureConsumer.newInstance(editorInfo, mInputLogic.getPrivateCommandPerformer(), - Arrays.asList(mSubtypeSwitcher.getCurrentSubtypeLocales()), + Arrays.asList(mRichImm.getCurrentSubtypeLocales()), switcher.getKeyboard()); // Forward this event to the accessibility utilities, if enabled. @@ -947,7 +947,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // span, so we should reset our state unconditionally, even if restarting is true. // We also tell the input logic about the combining rules for the current subtype, so // it can adjust its combiners if needed. - mInputLogic.startInput(mSubtypeSwitcher.getCombiningRulesExtraValueOfCurrentSubtype(), + mInputLogic.startInput(mRichImm.getCombiningRulesExtraValueOfCurrentSubtype(), currentSettingsValues); resetDictionaryFacilitatorIfNecessary(); @@ -1077,11 +1077,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd); } - // This call happens when we have a hardware keyboard as well as when we don't. While we - // don't support hardware keyboards yet we should avoid doing the processing associated - // with cursor movement when we have a hardware keyboard since we are not in charge. + // This call happens whether our view is displayed or not, but if it's not then we should + // not attempt recorrection. This is true even with a hardware keyboard connected: if the + // view is not displayed we have no means of showing suggestions anyway, and if it is then + // we want to show suggestions anyway. final SettingsValues settingsValues = mSettings.getCurrent(); - if ((!settingsValues.mHasHardwareKeyboard || ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) + if (isInputViewShown() && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, settingsValues)) { mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), @@ -1194,7 +1195,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (hasHardwareKeyboard && visibleKeyboardView.getVisibility() == View.GONE) { // If there is a hardware keyboard and a visible software keyboard view has been hidden, // no visual element will be shown on the screen. - outInsets.touchableInsets = inputHeight; + outInsets.contentTopInsets = inputHeight; outInsets.visibleTopInsets = inputHeight; mInsetsUpdater.setInsets(outInsets); return; @@ -1204,7 +1205,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen ? mSuggestionStripView.getHeight() : 0; final int visibleTopY = inputHeight - visibleKeyboardView.getHeight() - suggestionsHeight; mSuggestionStripView.setMoreSuggestionsHeight(visibleTopY); - // Need to set touchable region only if a keyboard view is being shown. + // Need to set expanded touchable region only if a keyboard view is being shown. if (visibleKeyboardView.isShown()) { final int touchLeft = 0; final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY; @@ -1423,7 +1424,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // completely replace #onCodeInput. public void onEvent(@Nonnull final Event event) { if (Constants.CODE_SHORTCUT == event.mKeyCode) { - mSubtypeSwitcher.switchToShortcutIME(this); + mRichImm.switchToShortcutIME(this); } final InputTransaction completeInputTransaction = mInputLogic.onCodeInput(mSettings.getCurrent(), event, @@ -1467,7 +1468,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void onStartBatchInput() { mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler); mGestureConsumer.onGestureStarted( - Arrays.asList(mSubtypeSwitcher.getCurrentSubtypeLocales()), + Arrays.asList(mRichImm.getCurrentSubtypeLocales()), mKeyboardSwitcher.getKeyboard()); } @@ -1489,11 +1490,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } /** - * To be called after the InputLogic has gotten a chance to act on the on-device decoding - * for the full gesture, possibly updating the TextView to reflect the first decoding. + * To be called after the InputLogic has gotten a chance to act on the suggested words by the + * IME for the full gesture, possibly updating the TextView to reflect the first suggestion. * <p> * This method must be run on the UI Thread. - * @param suggestedWords On-device decoding for the full gesture. + * @param suggestedWords suggested words by the IME for the full gesture. */ public void onTailBatchInputResultShown(final SuggestedWords suggestedWords) { mGestureConsumer.onImeSuggestionsProcessed(suggestedWords, @@ -1589,7 +1590,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // We should clear the contextual strip if there is no suggestion from dictionaries. || noSuggestionsFromDictionaries) { mSuggestionStripView.setSuggestions(suggestedWords, - mSubtypeSwitcher.getCurrentSubtype().isRtlSubtype()); + mRichImm.getCurrentSubtype().isRtlSubtype()); } } @@ -1810,7 +1811,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void onReceive(final Context context, final Intent intent) { final String action = intent.getAction(); if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - mSubtypeSwitcher.onNetworkStateChanged(intent); + mRichImm.onNetworkStateChanged(intent); } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged(); } diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java index bc8bd831c..7b1a53a6e 100644 --- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java @@ -16,8 +16,8 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.common.ComposedData; import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; import java.util.ArrayList; @@ -50,16 +50,16 @@ public final class ReadOnlyBinaryDictionary extends Dictionary { } @Override - public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final NgramContext ngramContext, final ProximityInfo proximityInfo, + public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, + final NgramContext ngramContext, final long proximityInfoHandle, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId, final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel) { if (mLock.readLock().tryLock()) { try { - return mBinaryDictionary.getSuggestions(composer, ngramContext, proximityInfo, - settingsValuesForSuggestion, sessionId, weightForLocale, - inOutWeightOfLangModelVsSpatialModel); + return mBinaryDictionary.getSuggestions(composedData, ngramContext, + proximityInfoHandle, settingsValuesForSuggestion, sessionId, + weightForLocale, inOutWeightOfLangModelVsSpatialModel); } finally { mLock.readLock().unlock(); } diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java index 8d8e7ac38..a1ac55a20 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -17,9 +17,15 @@ package com.android.inputmethod.latin; import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; +import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; +import android.inputmethodservice.InputMethodService; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.AsyncTask; import android.os.Build; import android.os.IBinder; import android.preference.PreferenceManager; @@ -28,7 +34,9 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils; import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; @@ -36,7 +44,11 @@ import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; import javax.annotation.Nonnull; @@ -46,6 +58,7 @@ import javax.annotation.Nonnull; // non final for easy mocking. public class RichInputMethodManager { private static final String TAG = RichInputMethodManager.class.getSimpleName(); + private static final boolean DEBUG = false; private RichInputMethodManager() { // This utility class is not publicly instantiable. @@ -56,6 +69,10 @@ public class RichInputMethodManager { private Context mContext; private InputMethodManagerCompatWrapper mImmWrapper; private InputMethodInfoCache mInputMethodInfoCache; + private RichInputMethodSubtype mCurrentRichInputMethodSubtype; + private InputMethodInfo mShortcutInputMethodInfo; + private InputMethodSubtype mShortcutSubtype; + private boolean mIsNetworkConnected; final HashMap<InputMethodInfo, List<InputMethodSubtype>> mSubtypeListCacheWithImplicitlySelectedSubtypes = new HashMap<>(); final HashMap<InputMethodInfo, List<InputMethodSubtype>> @@ -95,6 +112,11 @@ public class RichInputMethodManager { SubtypeLocaleUtils.init(context); final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes(context); setAdditionalInputMethodSubtypes(additionalSubtypes); + + final ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo info = connectivityManager.getActiveNetworkInfo(); + mIsNetworkConnected = (info != null && info.isConnected()); } public InputMethodSubtype[] getAdditionalSubtypes(final Context context) { @@ -304,10 +326,47 @@ public class RichInputMethodManager { } @Nonnull + public RichInputMethodSubtype onSubtypeChanged(@Nonnull final InputMethodSubtype newSubtype) { + final RichInputMethodSubtype richSubtype = createCurrentRichInputMethodSubtype(newSubtype); + if (DEBUG) { + Log.w(TAG, "onSubtypeChanged: " + richSubtype.getNameForLogging()); + } + mCurrentRichInputMethodSubtype = richSubtype; + return richSubtype; + } + + private static RichInputMethodSubtype sForcedSubtypeForTesting = null; + + @UsedForTesting + static void forceSubtype(final InputMethodSubtype subtype) { + sForcedSubtypeForTesting = new RichInputMethodSubtype(subtype); + } + + public Locale[] getCurrentSubtypeLocales() { + if (null != sForcedSubtypeForTesting) { + return sForcedSubtypeForTesting.getLocales(); + } + return getCurrentSubtype().getLocales(); + } + + public RichInputMethodSubtype getCurrentSubtype() { + if (null != sForcedSubtypeForTesting) { + return sForcedSubtypeForTesting; + } + return mCurrentRichInputMethodSubtype; + } + + + public String getCombiningRulesExtraValueOfCurrentSubtype() { + return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype()); + } + + @Nonnull public InputMethodSubtype getCurrentRawSubtype() { return mImmWrapper.mImm.getCurrentInputMethodSubtype(); } + @Nonnull public RichInputMethodSubtype createCurrentRichInputMethodSubtype( @Nonnull final InputMethodSubtype rawSubtype) { return AdditionalFeaturesSettingUtils.createRichInputMethodSubtype(this, rawSubtype, @@ -432,4 +491,121 @@ public class RichInputMethodManager { } return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder); } + + public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() { + final Locale systemLocale = mContext.getResources().getConfiguration().locale; + final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>(); + final InputMethodManager inputMethodManager = getInputMethodManager(); + final List<InputMethodInfo> enabledInputMethodInfoList = + inputMethodManager.getEnabledInputMethodList(); + for (final InputMethodInfo info : enabledInputMethodInfoList) { + final List<InputMethodSubtype> enabledSubtypes = + inputMethodManager.getEnabledInputMethodSubtypeList( + info, true /* allowsImplicitlySelectedSubtypes */); + if (enabledSubtypes.isEmpty()) { + // An IME with no subtypes is found. + return false; + } + enabledSubtypesOfEnabledImes.addAll(enabledSubtypes); + } + for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) { + if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty() + && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) { + return false; + } + } + return true; + } + + // TODO: Make this private + void updateShortcutIME() { + if (DEBUG) { + Log.d(TAG, "Update shortcut IME from : " + + (mShortcutInputMethodInfo == null + ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " + + (mShortcutSubtype == null ? "<null>" : ( + mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); + } + // TODO: Update an icon for shortcut IME + final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = + getInputMethodManager().getShortcutInputMethodsAndSubtypes(); + mShortcutInputMethodInfo = null; + mShortcutSubtype = null; + for (final InputMethodInfo imi : shortcuts.keySet()) { + final List<InputMethodSubtype> subtypes = shortcuts.get(imi); + // TODO: Returns the first found IMI for now. Should handle all shortcuts as + // appropriate. + mShortcutInputMethodInfo = imi; + // TODO: Pick up the first found subtype for now. Should handle all subtypes + // as appropriate. + mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; + break; + } + if (DEBUG) { + Log.d(TAG, "Update shortcut IME to : " + + (mShortcutInputMethodInfo == null + ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " + + (mShortcutSubtype == null ? "<null>" : ( + mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); + } + } + + public void switchToShortcutIME(final InputMethodService context) { + if (mShortcutInputMethodInfo == null) { + return; + } + + final String imiId = mShortcutInputMethodInfo.getId(); + switchToTargetIME(imiId, mShortcutSubtype, context); + } + + private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype, + final InputMethodService context) { + final IBinder token = context.getWindow().getWindow().getAttributes().token; + if (token == null) { + return; + } + final InputMethodManager imm = getInputMethodManager(); + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + imm.setInputMethodAndSubtype(token, imiId, subtype); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public boolean isShortcutImeEnabled() { + updateShortcutIME(); + if (mShortcutInputMethodInfo == null) { + return false; + } + if (mShortcutSubtype == null) { + return true; + } + return checkIfSubtypeBelongsToImeAndEnabled( + mShortcutInputMethodInfo, mShortcutSubtype); + } + + public boolean isShortcutImeReady() { + updateShortcutIME(); + if (mShortcutInputMethodInfo == null) { + return false; + } + if (mShortcutSubtype == null) { + return true; + } + if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) { + return mIsNetworkConnected; + } + return true; + } + + public void onNetworkStateChanged(final Intent intent) { + final boolean noConnection = intent.getBooleanExtra( + ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); + mIsNetworkConnected = !noConnection; + + KeyboardSwitcher.getInstance().onNetworkStateChanged(); + } } diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java index 8d055531c..ea8d4a210 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java @@ -16,33 +16,45 @@ package com.android.inputmethod.latin; +import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; + +import android.util.Log; import android.view.inputmethod.InputMethodSubtype; -import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; +import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import java.util.Arrays; import java.util.Locale; +import javax.annotation.Nonnull; + /** * Enrichment class for InputMethodSubtype to enable concurrent multi-lingual input. * * Right now, this returns the extra value of its primary subtype. */ public final class RichInputMethodSubtype { + private static final String TAG = RichInputMethodSubtype.class.getSimpleName(); + + @Nonnull private final InputMethodSubtype mSubtype; + @Nonnull private final Locale[] mLocales; - public RichInputMethodSubtype(final InputMethodSubtype subtype, final Locale... locales) { + public RichInputMethodSubtype(@Nonnull final InputMethodSubtype subtype, + @Nonnull final Locale... locales) { mSubtype = subtype; - mLocales = new Locale[locales.length+1]; + mLocales = new Locale[locales.length + 1]; mLocales[0] = LocaleUtils.constructLocaleFromString(mSubtype.getLocale()); System.arraycopy(locales, 0, mLocales, 1, locales.length); } // Extra values are determined by the primary subtype. This is probably right, but // we may have to revisit this later. - public String getExtraValueOf(final String key) { + public String getExtraValueOf(@Nonnull final String key) { return mSubtype.getExtraValueOf(key); } @@ -80,6 +92,7 @@ public final class RichInputMethodSubtype { // en_US azerty T English English (US) // zz azerty T AZERTY AZERTY // Get the RichInputMethodSubtype's full display name in its locale. + @Nonnull public String getFullDisplayName() { if (isNoLanguage()) { return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype); @@ -88,6 +101,7 @@ public final class RichInputMethodSubtype { } // Get the RichInputMethodSubtype's middle display name in its locale. + @Nonnull public String getMiddleDisplayName() { if (isNoLanguage()) { return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype); @@ -96,7 +110,7 @@ public final class RichInputMethodSubtype { } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (!(o instanceof RichInputMethodSubtype)) { return false; } @@ -114,15 +128,96 @@ public final class RichInputMethodSubtype { return "Multi-lingual subtype: " + mSubtype.toString() + ", " + Arrays.toString(mLocales); } + @Nonnull public Locale[] getLocales() { return mLocales; } public boolean isRtlSubtype() { // The subtype is considered RTL if the language of the main subtype is RTL. - return SubtypeLocaleUtils.isRtlLanguage(mLocales[0]); + return LocaleUtils.isRtlLanguage(mLocales[0]); } // TODO: remove this method + @Nonnull public InputMethodSubtype getRawSubtype() { return mSubtype; } + + @Nonnull + public String getKeyboardLayoutSetName() { + return SubtypeLocaleUtils.getKeyboardLayoutSetName(mSubtype); + } + + // Dummy no language QWERTY subtype. See {@link R.xml.method}. + private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3; + private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE = + "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY + + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE + + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE + + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; + @Nonnull + private static final RichInputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = + new RichInputMethodSubtype(InputMethodSubtypeCompatUtils.newInputMethodSubtype( + R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark, + SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, + EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE, + false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, + SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE)); + // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}. + // Dummy Emoji subtype. See {@link R.xml.method}. + private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0; + private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE = + "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI + + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; + @Nonnull + private static final RichInputMethodSubtype DUMMY_EMOJI_SUBTYPE = new RichInputMethodSubtype( + InputMethodSubtypeCompatUtils.newInputMethodSubtype( + R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark, + SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, + EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE, + false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, + SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE)); + private static RichInputMethodSubtype sNoLanguageSubtype; + private static RichInputMethodSubtype sEmojiSubtype; + + @Nonnull + public static RichInputMethodSubtype getNoLanguageSubtype() { + RichInputMethodSubtype noLanguageSubtype = sNoLanguageSubtype; + if (noLanguageSubtype == null) { + final InputMethodSubtype rawNoLanguageSubtype = RichInputMethodManager.getInstance() + .findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY); + if (rawNoLanguageSubtype != null) { + noLanguageSubtype = new RichInputMethodSubtype(rawNoLanguageSubtype); + } + } + if (noLanguageSubtype != null) { + sNoLanguageSubtype = noLanguageSubtype; + return noLanguageSubtype; + } + Log.w(TAG, "Can't find any language with QWERTY subtype"); + Log.w(TAG, "No input method subtype found; returning dummy subtype: " + + DUMMY_NO_LANGUAGE_SUBTYPE); + return DUMMY_NO_LANGUAGE_SUBTYPE; + } + + @Nonnull + public static RichInputMethodSubtype getEmojiSubtype() { + RichInputMethodSubtype emojiSubtype = sEmojiSubtype; + if (emojiSubtype == null) { + final InputMethodSubtype rawEmojiSubtype = RichInputMethodManager.getInstance() + .findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI); + if (rawEmojiSubtype != null) { + emojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype); + } + } + if (emojiSubtype != null) { + sEmojiSubtype = emojiSubtype; + return emojiSubtype; + } + Log.w(TAG, "Can't find emoji subtype"); + Log.w(TAG, "No input method subtype found; returning dummy subtype: " + + DUMMY_EMOJI_SUBTYPE); + return DUMMY_EMOJI_SUBTYPE; + } } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 98bce95bd..23e348bff 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -16,40 +16,18 @@ package com.android.inputmethod.latin; -import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY; import android.content.Context; -import android.content.Intent; import android.content.res.Resources; -import android.inputmethodservice.InputMethodService; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.AsyncTask; -import android.os.IBinder; -import android.util.Log; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; -import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper; -import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.define.DebugFlags; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; -import java.util.HashSet; import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; import javax.annotation.Nonnull; public final class SubtypeSwitcher { - private static boolean DBG = DebugFlags.DEBUG_ENABLED; - private static final String TAG = SubtypeSwitcher.class.getSimpleName(); - private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); private /* final */ RichInputMethodManager mRichImm; @@ -57,41 +35,6 @@ public final class SubtypeSwitcher { private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper = new LanguageOnSpacebarHelper(); - private InputMethodInfo mShortcutInputMethodInfo; - private InputMethodSubtype mShortcutSubtype; - private RichInputMethodSubtype mCurrentRichInputMethodSubtype; - private RichInputMethodSubtype mNoLanguageSubtype; - private RichInputMethodSubtype mEmojiSubtype; - private boolean mIsNetworkConnected; - - private static final String KEYBOARD_MODE = "keyboard"; - // Dummy no language QWERTY subtype. See {@link R.xml.method}. - private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3; - private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE = - "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY - + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE - + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE - + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; - private static final RichInputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = - new RichInputMethodSubtype(InputMethodSubtypeCompatUtils.newInputMethodSubtype( - R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark, - SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, - EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE, - false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, - SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE)); - // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}. - // Dummy Emoji subtype. See {@link R.xml.method}. - private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0; - private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE = - "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI - + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE; - private static final RichInputMethodSubtype DUMMY_EMOJI_SUBTYPE = new RichInputMethodSubtype( - InputMethodSubtypeCompatUtils.newInputMethodSubtype( - R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark, - SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE, - EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE, - false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, - SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE)); public static SubtypeSwitcher getInstance() { return sInstance; @@ -113,18 +56,9 @@ public final class SubtypeSwitcher { } mResources = context.getResources(); mRichImm = RichInputMethodManager.getInstance(); - ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); - - final NetworkInfo info = connectivityManager.getActiveNetworkInfo(); - mIsNetworkConnected = (info != null && info.isConnected()); - - refreshSubtypeInfo(); - updateParametersOnStartInputView(); - } - public void refreshSubtypeInfo() { onSubtypeChanged(mRichImm.getCurrentRawSubtype()); + updateParametersOnStartInputView(); } /** @@ -134,219 +68,21 @@ public final class SubtypeSwitcher { public void updateParametersOnStartInputView() { final List<InputMethodSubtype> enabledSubtypesOfThisIme = mRichImm.getMyEnabledInputMethodSubtypeList(true); - mLanguageOnSpacebarHelper.updateEnabledSubtypes(enabledSubtypesOfThisIme); - updateShortcutIME(); - } - - private void updateShortcutIME() { - if (DBG) { - Log.d(TAG, "Update shortcut IME from : " - + (mShortcutInputMethodInfo == null - ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " - + (mShortcutSubtype == null ? "<null>" : ( - mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); - } - // TODO: Update an icon for shortcut IME - final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = - mRichImm.getInputMethodManager().getShortcutInputMethodsAndSubtypes(); - mShortcutInputMethodInfo = null; - mShortcutSubtype = null; - for (final InputMethodInfo imi : shortcuts.keySet()) { - final List<InputMethodSubtype> subtypes = shortcuts.get(imi); - // TODO: Returns the first found IMI for now. Should handle all shortcuts as - // appropriate. - mShortcutInputMethodInfo = imi; - // TODO: Pick up the first found subtype for now. Should handle all subtypes - // as appropriate. - mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; - break; - } - if (DBG) { - Log.d(TAG, "Update shortcut IME to : " - + (mShortcutInputMethodInfo == null - ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " - + (mShortcutSubtype == null ? "<null>" : ( - mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); - } + mLanguageOnSpacebarHelper.onUpdateEnabledSubtypes(enabledSubtypesOfThisIme); + mRichImm.updateShortcutIME(); } // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. public void onSubtypeChanged(@Nonnull final InputMethodSubtype newSubtype) { - final RichInputMethodSubtype richSubtype = - mRichImm.createCurrentRichInputMethodSubtype(newSubtype); - if (DBG) { - Log.w(TAG, "onSubtypeChanged: " + richSubtype.getNameForLogging()); - } - mCurrentRichInputMethodSubtype = richSubtype; - final Locale[] newLocales = richSubtype.getLocales(); - if (newLocales.length > 1) { - // In multi-locales mode, the system language is never the same as the input language - // because there is no single input language. - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false); - } else { - final Locale newLocale = newLocales[0]; - final Locale systemLocale = mResources.getConfiguration().locale; - final boolean sameLocale = systemLocale.equals(newLocale); - final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage()); - final boolean implicitlyEnabled = mRichImm - .checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype); - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage( - sameLocale || (sameLanguage && implicitlyEnabled)); - } - updateShortcutIME(); - } - - //////////////////////////// - // Shortcut IME functions // - //////////////////////////// - - public void switchToShortcutIME(final InputMethodService context) { - if (mShortcutInputMethodInfo == null) { - return; - } - - final String imiId = mShortcutInputMethodInfo.getId(); - switchToTargetIME(imiId, mShortcutSubtype, context); - } - - private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype, - final InputMethodService context) { - final IBinder token = context.getWindow().getWindow().getAttributes().token; - if (token == null) { - return; - } - final InputMethodManager imm = mRichImm.getInputMethodManager(); - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - imm.setInputMethodAndSubtype(token, imiId, subtype); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public boolean isShortcutImeEnabled() { - updateShortcutIME(); - if (mShortcutInputMethodInfo == null) { - return false; - } - if (mShortcutSubtype == null) { - return true; - } - return mRichImm.checkIfSubtypeBelongsToImeAndEnabled( - mShortcutInputMethodInfo, mShortcutSubtype); - } - - public boolean isShortcutImeReady() { - updateShortcutIME(); - if (mShortcutInputMethodInfo == null) { - return false; - } - if (mShortcutSubtype == null) { - return true; - } - if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) { - return mIsNetworkConnected; - } - return true; - } - - public void onNetworkStateChanged(final Intent intent) { - final boolean noConnection = intent.getBooleanExtra( - ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); - mIsNetworkConnected = !noConnection; - - KeyboardSwitcher.getInstance().onNetworkStateChanged(); + final RichInputMethodSubtype richSubtype = mRichImm.onSubtypeChanged(newSubtype); + final boolean implicitlyEnabledSubtype = mRichImm + .checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype); + mLanguageOnSpacebarHelper.onSubtypeChanged( + richSubtype, implicitlyEnabledSubtype, mResources.getConfiguration().locale); + mRichImm.updateShortcutIME(); } - ////////////////////////////////// - // Subtype Switching functions // - ////////////////////////////////// - public int getLanguageOnSpacebarFormatType(final RichInputMethodSubtype subtype) { return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype); } - - public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() { - final Locale systemLocale = mResources.getConfiguration().locale; - final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>(); - final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager(); - final List<InputMethodInfo> enabledInputMethodInfoList = - inputMethodManager.getEnabledInputMethodList(); - for (final InputMethodInfo info : enabledInputMethodInfoList) { - final List<InputMethodSubtype> enabledSubtypes = - inputMethodManager.getEnabledInputMethodSubtypeList( - info, true /* allowsImplicitlySelectedSubtypes */); - if (enabledSubtypes.isEmpty()) { - // An IME with no subtypes is found. - return false; - } - enabledSubtypesOfEnabledImes.addAll(enabledSubtypes); - } - for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) { - if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty() - && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) { - return false; - } - } - return true; - } - - private static RichInputMethodSubtype sForcedSubtypeForTesting = null; - - @UsedForTesting - static void forceSubtype(final InputMethodSubtype subtype) { - sForcedSubtypeForTesting = new RichInputMethodSubtype(subtype); - } - - public Locale[] getCurrentSubtypeLocales() { - if (null != sForcedSubtypeForTesting) { - return sForcedSubtypeForTesting.getLocales(); - } - return getCurrentSubtype().getLocales(); - } - - public RichInputMethodSubtype getCurrentSubtype() { - if (null != sForcedSubtypeForTesting) { - return sForcedSubtypeForTesting; - } - return mCurrentRichInputMethodSubtype; - } - - public RichInputMethodSubtype getNoLanguageSubtype() { - if (mNoLanguageSubtype == null) { - mNoLanguageSubtype = new RichInputMethodSubtype( - mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY)); - } - if (mNoLanguageSubtype != null) { - return mNoLanguageSubtype; - } - Log.w(TAG, "Can't find any language with QWERTY subtype"); - Log.w(TAG, "No input method subtype found; returning dummy subtype: " - + DUMMY_NO_LANGUAGE_SUBTYPE); - return DUMMY_NO_LANGUAGE_SUBTYPE; - } - - public RichInputMethodSubtype getEmojiSubtype() { - if (mEmojiSubtype == null) { - final InputMethodSubtype rawEmojiSubtype = - mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI); - if (null != rawEmojiSubtype) { - mEmojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype); - } - } - if (mEmojiSubtype != null) { - return mEmojiSubtype; - } - Log.w(TAG, "Can't find emoji subtype"); - Log.w(TAG, "No input method subtype found; returning dummy subtype: " - + DUMMY_EMOJI_SUBTYPE); - return DUMMY_EMOJI_SUBTYPE; - } - - public String getCombiningRulesExtraValueOfCurrentSubtype() { - return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype()); - } } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 430f765ea..ee8d3f8cb 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -140,8 +140,8 @@ public final class Suggest { : typedWord; final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( - wordComposer, ngramContext, proximityInfo, settingsValuesForSuggestion, - SESSION_ID_TYPING); + wordComposer, ngramContext, proximityInfo.getNativeProximityInfo(), + settingsValuesForSuggestion, SESSION_ID_TYPING); final ArrayList<SuggestedWordInfo> suggestionsContainer = getTransformedSuggestedWordInfoList(wordComposer, suggestionResults, trailingSingleQuotesCount, @@ -230,7 +230,7 @@ public final class Suggest { inputStyle = inputStyleIfNotPrediction; } callback.onGetSuggestedWords(new SuggestedWords(suggestionsList, - suggestionResults.mRawSuggestions, + suggestionResults.mRawSuggestions, typedWord, // TODO: this first argument is lying. If this is a whitelisted word which is an // actual word, it says typedWordValid = false, which looks wrong. We should either // rename the attribute or change the value. @@ -247,8 +247,8 @@ public final class Suggest { final int inputStyle, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( - wordComposer, ngramContext, proximityInfo, settingsValuesForSuggestion, - SESSION_ID_GESTURE); + wordComposer, ngramContext, proximityInfo.getNativeProximityInfo(), + settingsValuesForSuggestion, SESSION_ID_GESTURE); // For transforming words that don't come from a dictionary, because it's our best bet final Locale defaultLocale = mDictionaryFacilitator.getMostProbableLocale(); final ArrayList<SuggestedWordInfo> suggestionsContainer = @@ -286,8 +286,12 @@ public final class Suggest { // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false). // Note that because this method is never used to get predictions, there is no need to // modify inputType such in getSuggestedWordsForNonBatchInput. + final String pseudoTypedWord = suggestionsContainer.isEmpty() ? null + : suggestionsContainer.get(0).mWord; + callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer, suggestionResults.mRawSuggestions, + pseudoTypedWord, true /* typedWordValid */, false /* willAutoCorrect */, false /* isObsoleteSuggestions */, diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index c51e20f21..bddeac495 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -73,21 +73,11 @@ public class SuggestedWords { final boolean willAutoCorrect, final boolean isObsoleteSuggestions, final int inputStyle) { - this(suggestedWordInfoList, rawSuggestions, typedWordValid, willAutoCorrect, - isObsoleteSuggestions, inputStyle, NOT_A_SEQUENCE_NUMBER); - } - - public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, - final ArrayList<SuggestedWordInfo> rawSuggestions, - final boolean typedWordValid, - final boolean willAutoCorrect, - final boolean isObsoleteSuggestions, - final int inputStyle, - final int sequenceNumber) { this(suggestedWordInfoList, rawSuggestions, (suggestedWordInfoList.isEmpty() || isPrediction(inputStyle)) ? null : suggestedWordInfoList.get(INDEX_OF_TYPED_WORD).mWord, - typedWordValid, willAutoCorrect, isObsoleteSuggestions, inputStyle, sequenceNumber); + typedWordValid, willAutoCorrect, + isObsoleteSuggestions, inputStyle, NOT_A_SEQUENCE_NUMBER); } public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 0b77f2ce3..78860d87d 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -19,11 +19,12 @@ package com.android.inputmethod.latin; import com.android.inputmethod.event.CombinerChain; import com.android.inputmethod.event.Event; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.common.ComposedData; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.CoordinateUtils; import com.android.inputmethod.latin.common.InputPointers; import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.utils.CoordinateUtils; import java.util.ArrayList; import java.util.Collections; @@ -90,6 +91,10 @@ public final class WordComposer { refreshTypedWordCache(); } + public ComposedData getComposedDataSnapshot() { + return new ComposedData(getInputPointers(), isBatchMode(), mTypedWordCache.toString()); + } + /** * Restart the combiners, possibly with a new spec. * @param combiningSpec The spec string for combining. This is found in the extra value. @@ -134,38 +139,6 @@ public final class WordComposer { return mCodePointSize; } - /** - * Copy the code points in the typed word to a destination array of ints. - * - * If the array is too small to hold the code points in the typed word, nothing is copied and - * -1 is returned. - * - * @param destination the array of ints. - * @return the number of copied code points. - */ - public int copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount( - final int[] destination) { - // This method can be called on a separate thread and mTypedWordCache can change while we - // are executing this method. - final String typedWord = mTypedWordCache.toString(); - // lastIndex is exclusive - final int lastIndex = typedWord.length() - - StringUtils.getTrailingSingleQuotesCount(typedWord); - if (lastIndex <= 0) { - // The string is empty or contains only single quotes. - return 0; - } - - // The following function counts the number of code points in the text range which begins - // at index 0 and extends to the character at lastIndex. - final int codePointSize = Character.codePointCount(typedWord, 0, lastIndex); - if (codePointSize > destination.length) { - return -1; - } - return StringUtils.copyCodePointsAndReturnCodePointCount(destination, typedWord, 0, - lastIndex, true /* downCase */); - } - public boolean isSingleLetter() { return size() == 1; } diff --git a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java index d4be0e397..78bfd2b52 100644 --- a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java +++ b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java @@ -26,10 +26,10 @@ import android.os.Environment; import com.android.inputmethod.latin.BinaryDictionaryFileDumper; import com.android.inputmethod.latin.BinaryDictionaryGetter; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.utils.DialogUtils; import com.android.inputmethod.latin.utils.DictionaryInfoUtils; -import com.android.inputmethod.latin.utils.LocaleUtils; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index 78d79ae50..eba9654a5 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -171,14 +171,18 @@ public final class FormatSpec { // ExpandableDictionary.matchesExpectedBinaryDictFormatVersionForThisType(). public static final int VERSION2 = 2; public static final int VERSION201 = 201; + public static final int VERSION202 = 202; public static final int MINIMUM_SUPPORTED_VERSION_OF_CODE_POINT_TABLE = VERSION201; // Dictionary version used for testing. public static final int VERSION4_ONLY_FOR_TESTING = 399; - public static final int VERSION401 = 401; - public static final int VERSION4 = 402; - public static final int VERSION4_DEV = 403; - static final int MINIMUM_SUPPORTED_VERSION = VERSION2; - static final int MAXIMUM_SUPPORTED_VERSION = VERSION4_DEV; + public static final int VERSION402 = 402; + public static final int VERSION403 = 403; + public static final int VERSION4 = VERSION403; + public static final int VERSION4_DEV = VERSION403; + public static final int MINIMUM_SUPPORTED_STATIC_VERSION = VERSION202; + public static final int MAXIMUM_SUPPORTED_STATIC_VERSION = VERSION202; + static final int MINIMUM_SUPPORTED_DYNAMIC_VERSION = VERSION4; + static final int MAXIMUM_SUPPORTED_DYNAMIC_VERSION = VERSION4_DEV; // TODO: Make this value adaptative to content data, store it in the header, and // use it in the reading code. diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java index 331f85e0e..8c5eb0aa7 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java @@ -19,7 +19,7 @@ package com.android.inputmethod.latin.personalization; import android.content.Context; import android.util.Log; -import com.android.inputmethod.latin.utils.FileUtils; +import com.android.inputmethod.latin.common.FileUtils; import java.io.File; import java.io.FilenameFilter; @@ -28,32 +28,45 @@ import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Helps handle and manage personalized dictionaries such as {@link UserHistoryDictionary} and + * {@link PersonalizationDictionary}. + */ public class PersonalizationHelper { private static final String TAG = PersonalizationHelper.class.getSimpleName(); private static final boolean DEBUG = false; + private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> sLangUserHistoryDictCache = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>> sLangPersonalizationDictCache = new ConcurrentHashMap<>(); + @Nonnull public static UserHistoryDictionary getUserHistoryDictionary( - final Context context, final Locale locale) { - final String localeStr = locale.toString(); + final Context context, final Locale locale, @Nullable final String accountName) { + String lookupStr = locale.toString(); + if (accountName != null) { + lookupStr += "." + accountName; + } synchronized (sLangUserHistoryDictCache) { - if (sLangUserHistoryDictCache.containsKey(localeStr)) { + if (sLangUserHistoryDictCache.containsKey(lookupStr)) { final SoftReference<UserHistoryDictionary> ref = - sLangUserHistoryDictCache.get(localeStr); + sLangUserHistoryDictCache.get(lookupStr); final UserHistoryDictionary dict = ref == null ? null : ref.get(); if (dict != null) { if (DEBUG) { - Log.w(TAG, "Use cached UserHistoryDictionary for " + locale); + Log.d(TAG, "Use cached UserHistoryDictionary for " + locale + + " & account" + accountName); } dict.reloadDictionaryIfRequired(); return dict; } } final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale); - sLangUserHistoryDictCache.put(localeStr, new SoftReference<>(dict)); + sLangUserHistoryDictCache.put(lookupStr, new SoftReference<>(dict)); return dict; } } diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java index 58782c646..946835cbc 100644 --- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java @@ -17,30 +17,73 @@ package com.android.inputmethod.latin.personalization; import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import com.android.inputmethod.annotations.ExternallyReferenced; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.ExpandableBinaryDictionary; import com.android.inputmethod.latin.NgramContext; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.define.ProductionFlags; +import com.android.inputmethod.latin.settings.LocalSettingsConstants; import com.android.inputmethod.latin.utils.DistracterFilter; import java.io.File; import java.util.Locale; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Locally gathers stats about the words user types and various other signals like auto-correction * cancellation or manual picks. This allows the keyboard to adapt to the typist over time. */ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase { - /* package */ static final String NAME = UserHistoryDictionary.class.getSimpleName(); + static final String NAME = UserHistoryDictionary.class.getSimpleName(); // TODO: Make this constructor private - /* package */ UserHistoryDictionary(final Context context, final Locale locale) { - super(context, getDictName(NAME, locale, null /* dictFile */), locale, - Dictionary.TYPE_USER_HISTORY, null /* dictFile */); + UserHistoryDictionary(final Context context, final Locale locale) { + super(context, + getUserHistoryDictName( + NAME, + locale, + null /* dictFile */, + context), + locale, + Dictionary.TYPE_USER_HISTORY, + null /* dictFile */); + } + + /** + * @returns the name of the {@link UserHistoryDictionary}. + */ + @UsedForTesting + static String getUserHistoryDictName(final String name, final Locale locale, + @Nullable final File dictFile, final Context context) { + if (!ProductionFlags.ENABLE_PER_ACCOUNT_USER_HISTORY_DICTIONARY) { + return getDictName(name, locale, dictFile); + } + return getUserHistoryDictNamePerAccount(name, locale, dictFile, context); + } + + /** + * Uses the currently signed in account to determine the dictionary name. + */ + private static String getUserHistoryDictNamePerAccount(final String name, final Locale locale, + @Nullable final File dictFile, final Context context) { + if (dictFile != null) { + return dictFile.getName(); + } + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + final String account = prefs.getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, + null /* default */); + String dictName = name + "." + locale.toString(); + if (account != null) { + dictName += "." + account; + } + return dictName; } // Note: This method is called by {@link DictionaryFacilitator} using Java reflection. @@ -48,7 +91,14 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas @ExternallyReferenced public static UserHistoryDictionary getDictionary(final Context context, final Locale locale, final File dictFile, final String dictNamePrefix) { - return PersonalizationHelper.getUserHistoryDictionary(context, locale); + final String account; + if (ProductionFlags.ENABLE_PER_ACCOUNT_USER_HISTORY_DICTIONARY) { + account = PreferenceManager.getDefaultSharedPreferences(context) + .getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, null /* default */); + } else { + account = null; + } + return PersonalizationHelper.getUserHistoryDictionary(context, locale, account); } /** diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java index 01398f467..b749aa51a 100644 --- a/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java +++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java @@ -346,8 +346,10 @@ final class CustomInputStylePreference extends DialogPreference super(context, android.R.layout.simple_spinner_item); setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + final String[] predefinedKeyboardLayoutSet = context.getResources().getStringArray( + R.array.predefined_layouts); // TODO: Should filter out already existing combinations of locale and layout. - for (final String layout : SubtypeLocaleUtils.getPredefinedKeyboardLayoutSet()) { + for (final String layout : predefinedKeyboardLayoutSet) { // This is a dummy subtype with NO_LANGUAGE, only for display. final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype( diff --git a/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java index 49db2bdc0..c0ceb8857 100644 --- a/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java @@ -24,7 +24,7 @@ import android.preference.Preference; import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.RichInputMethodManager; /** * "Preferences" settings sub screen. @@ -49,7 +49,7 @@ public final class PreferencesSettingsFragment extends SubScreenFragment { // When we are called from the Settings application but we are not already running, some // singleton and utility classes may not have been initialized. We have to call // initialization method of these classes here. See {@link LatinIME#onCreate()}. - SubtypeSwitcher.init(context); + RichInputMethodManager.init(context); final boolean showVoiceKeyOption = res.getBoolean( R.bool.config_enable_show_voice_key_option); @@ -71,7 +71,7 @@ public final class PreferencesSettingsFragment extends SubScreenFragment { super.onResume(); final Preference voiceInputKeyOption = findPreference(Settings.PREF_VOICE_INPUT_KEY); if (voiceInputKeyOption != null) { - final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance() + final boolean isShortcutImeEnabled = RichInputMethodManager.getInstance() .isShortcutImeEnabled(); voiceInputKeyOption.setEnabled(isShortcutImeEnabled); voiceInputKeyOption.setSummary( diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index 509b41fd3..26415e7d4 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -28,7 +28,6 @@ import com.android.inputmethod.compat.AppWorkaroundsUtils; import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; -import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.ResourceUtils; import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; @@ -140,7 +139,7 @@ public class SettingsValues { DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true); mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res) && mInputAttributes.mShouldShowVoiceInputKey - && SubtypeSwitcher.getInstance().isShortcutImeEnabled(); + && RichInputMethodManager.getInstance().isShortcutImeEnabled(); final String autoCorrectionThresholdRawValue = prefs.getString( Settings.PREF_AUTO_CORRECTION_THRESHOLD, res.getString(R.string.auto_correction_threshold_mode_index_modest)); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 315e3696b..bcf7bbfdc 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -168,7 +168,8 @@ public final class AndroidSpellCheckerService extends SpellCheckerService DictionaryFacilitator dictionaryFacilitatorForLocale = mDictionaryFacilitatorCache.get(locale); return dictionaryFacilitatorForLocale.getSuggestionResults(composer, ngramContext, - proximityInfo, mSettingsValuesForSuggestion, sessionId); + proximityInfo.getNativeProximityInfo(), mSettingsValuesForSuggestion, + sessionId); } finally { if (sessionId != null) { mSessionIdPool.add(sessionId); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 3ad8fb910..c90e8a3cf 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -34,10 +34,10 @@ import com.android.inputmethod.latin.NgramContext; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.CoordinateUtils; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.CoordinateUtils; -import com.android.inputmethod.latin.utils.LocaleUtils; import com.android.inputmethod.latin.utils.ScriptUtils; import com.android.inputmethod.latin.utils.SuggestionResults; diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index 27a0f62ff..7991a2473 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -50,10 +50,10 @@ import com.android.inputmethod.latin.PunctuationSuggestions; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.settings.SettingsValues; import com.android.inputmethod.latin.utils.ResourceUtils; -import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import com.android.inputmethod.latin.utils.ViewLayoutUtils; import java.util.ArrayList; @@ -570,8 +570,7 @@ final class SuggestionStripLayoutHelper { final boolean isRtlLanguage = (ViewCompat.getLayoutDirection(addToDictionaryStrip) == ViewCompat.LAYOUT_DIRECTION_RTL); final String arrow = isRtlLanguage ? RIGHTWARDS_ARROW : LEFTWARDS_ARROW; - final boolean isRtlSystem = SubtypeLocaleUtils.isRtlLanguage( - res.getConfiguration().locale); + final boolean isRtlSystem = LocaleUtils.isRtlLanguage(res.getConfiguration().locale); final CharSequence hint = res.getText(R.string.hint_add_to_dictionary); hintText = (isRtlLanguage == isRtlSystem) ? (arrow + hint) : (hint + arrow); hintWidth = width - wordWidth; diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java index eda81940f..22fc35a42 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java @@ -28,7 +28,7 @@ import android.widget.EditText; import com.android.inputmethod.compat.UserDictionaryCompatUtils; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.latin.common.LocaleUtils; import java.util.ArrayList; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java index 90e4faafd..b9ed35375 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java @@ -31,7 +31,7 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.latin.common.LocaleUtils; import java.util.List; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java index e58727ec4..c0a946e42 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java @@ -17,7 +17,7 @@ package com.android.inputmethod.latin.userdictionary; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.latin.common.LocaleUtils; import android.content.Context; import android.text.TextUtils; diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java index 0db63fd9f..0dbc7c858 100644 --- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java @@ -24,6 +24,7 @@ import com.android.inputmethod.latin.common.Constants; import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; +import java.util.ArrayList; import java.util.Locale; public final class CapsModeUtils { @@ -326,4 +327,31 @@ public final class CapsModeUtils { // Here we arrived at the start of the line. This should behave exactly like whitespace. return (START == state || LETTER == state) ? noCaps : caps; } + + /** + * Convert capitalize mode flags into human readable text. + * + * @param capsFlags The modes flags to be converted. It may be any combination of + * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and + * {@link TextUtils#CAP_MODE_SENTENCES}. + * @return the text that describe the <code>capsMode</code>. + */ + public static String flagsToString(final int capsFlags) { + final int capsFlagsMask = TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS + | TextUtils.CAP_MODE_SENTENCES; + if ((capsFlags & ~capsFlagsMask) != 0) { + return "unknown<0x" + Integer.toHexString(capsFlags) + ">"; + } + final ArrayList<String> builder = new ArrayList<>(); + if ((capsFlags & android.text.TextUtils.CAP_MODE_CHARACTERS) != 0) { + builder.add("characters"); + } + if ((capsFlags & android.text.TextUtils.CAP_MODE_WORDS) != 0) { + builder.add("words"); + } + if ((capsFlags & android.text.TextUtils.CAP_MODE_SENTENCES) != 0) { + builder.add("sentences"); + } + return builder.isEmpty() ? "none" : TextUtils.join("|", builder); + } } diff --git a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java index 4e0f5f583..8699f2ce7 100644 --- a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java @@ -36,7 +36,8 @@ public class CombinedFormatUtils { public static final String WORD_TAG = "word"; public static final String BEGINNING_OF_SENTENCE_TAG = "beginning_of_sentence"; public static final String NOT_A_WORD_TAG = "not_a_word"; - public static final String BLACKLISTED_TAG = "blacklisted"; + public static final String POSSIBLY_OFFENSIVE_TAG = "possibly_offensive"; + public static final String TRUE_VALUE = "true"; public static String formatAttributeMap(final HashMap<String, String> attributeMap) { final StringBuilder builder = new StringBuilder(); @@ -61,13 +62,13 @@ public class CombinedFormatUtils { builder.append(","); builder.append(formatProbabilityInfo(wordProperty.mProbabilityInfo)); if (wordProperty.mIsBeginningOfSentence) { - builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true"); + builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=" + TRUE_VALUE); } if (wordProperty.mIsNotAWord) { - builder.append("," + NOT_A_WORD_TAG + "=true"); + builder.append("," + NOT_A_WORD_TAG + "=" + TRUE_VALUE); } if (wordProperty.mIsPossiblyOffensive) { - builder.append("," + BLACKLISTED_TAG + "=true"); + builder.append("," + POSSIBLY_OFFENSIVE_TAG + "=" + TRUE_VALUE); } builder.append("\n"); if (wordProperty.mHasShortcuts) { @@ -111,4 +112,8 @@ public class CombinedFormatUtils { } return builder.toString(); } + + public static boolean isLiteralTrue(final String value) { + return TRUE_VALUE.equalsIgnoreCase(value); + } } diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java index 24025b272..81c3e3c61 100644 --- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java @@ -28,6 +28,7 @@ import com.android.inputmethod.latin.AssetFileAddress; import com.android.inputmethod.latin.BinaryDictionaryGetter; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java index 56c8249bd..9c6a94810 100644 --- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java @@ -250,8 +250,9 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr composer.setComposingWord(codePoints, coordinates); final SuggestionResults suggestionResults; synchronized (mLock) { - suggestionResults = dictionaryFacilitator.getSuggestionResults( - composer, NgramContext.EMPTY_PREV_WORDS_INFO, keyboard.getProximityInfo(), + suggestionResults = dictionaryFacilitator.getSuggestionResults(composer, + NgramContext.EMPTY_PREV_WORDS_INFO, + keyboard.getProximityInfo().getNativeProximityInfo(), settingsValuesForSuggestion, 0 /* sessionId */); } if (suggestionResults.isEmpty()) { diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java deleted file mode 100644 index c519a0de6..000000000 --- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin.utils; - -import android.text.TextUtils; - -import java.util.HashMap; -import java.util.Locale; - -/** - * A class to help with handling Locales in string form. - * - * This file has the same meaning and features (and shares all of its code) with - * the one in the dictionary pack. They need to be kept synchronized; for any - * update/bugfix to this file, consider also updating/fixing the version in the - * dictionary pack. - */ -public final class LocaleUtils { - private LocaleUtils() { - // Intentional empty constructor for utility class. - } - - // Locale match level constants. - // A higher level of match is guaranteed to have a higher numerical value. - // Some room is left within constants to add match cases that may arise necessary - // in the future, for example differentiating between the case where the countries - // are both present and different, and the case where one of the locales does not - // specify the countries. This difference is not needed now. - - // Nothing matches. - public static final int LOCALE_NO_MATCH = 0; - // The languages matches, but the country are different. Or, the reference locale requires a - // country and the tested locale does not have one. - public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3; - // The languages and country match, but the variants are different. Or, the reference locale - // requires a variant and the tested locale does not have one. - public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6; - // The required locale is null or empty so it will accept anything, and the tested locale - // is non-null and non-empty. - public static final int LOCALE_ANY_MATCH = 10; - // The language matches, and the tested locale specifies a country but the reference locale - // does not require one. - public static final int LOCALE_LANGUAGE_MATCH = 15; - // The language and the country match, and the tested locale specifies a variant but the - // reference locale does not require one. - public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20; - // The compared locales are fully identical. This is the best match level. - public static final int LOCALE_FULL_MATCH = 30; - - // The level at which a match is "normally" considered a locale match with standard algorithms. - // Don't use this directly, use #isMatch to test. - private static final int LOCALE_MATCH = LOCALE_ANY_MATCH; - - // Make this match the maximum match level. If this evolves to have more than 2 digits - // when written in base 10, also adjust the getMatchLevelSortedString method. - private static final int MATCH_LEVEL_MAX = 30; - - /** - * Return how well a tested locale matches a reference locale. - * - * This will check the tested locale against the reference locale and return a measure of how - * a well it matches the reference. The general idea is that the tested locale has to match - * every specified part of the required locale. A full match occur when they are equal, a - * partial match when the tested locale agrees with the reference locale but is more specific, - * and a difference when the tested locale does not comply with all requirements from the - * reference locale. - * In more detail, if the reference locale specifies at least a language and the testedLocale - * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the - * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH - * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and - * tested locale agree on the language, but not on the country, - * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country, - * and LOCALE_LANGUAGE_MATCH otherwise. - * If they agree on both the language and the country, but not on the variant, - * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale - * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches, - * LOCALE_FULL_MATCH is returned. - * Examples: - * en <=> en_US => LOCALE_LANGUAGE_MATCH - * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER - * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER - * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH - * sp_US <=> en_US => LOCALE_NO_MATCH - * de <=> de => LOCALE_FULL_MATCH - * en_US <=> en_US => LOCALE_FULL_MATCH - * "" <=> en_US => LOCALE_ANY_MATCH - * - * @param referenceLocale the reference locale to test against. - * @param testedLocale the locale to test. - * @return a constant that measures how well the tested locale matches the reference locale. - */ - public static int getMatchLevel(String referenceLocale, String testedLocale) { - if (TextUtils.isEmpty(referenceLocale)) { - return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH; - } - if (null == testedLocale) return LOCALE_NO_MATCH; - String[] referenceParams = referenceLocale.split("_", 3); - String[] testedParams = testedLocale.split("_", 3); - // By spec of String#split, [0] cannot be null and length cannot be 0. - if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH; - switch (referenceParams.length) { - case 1: - return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH; - case 2: - if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (!referenceParams[1].equals(testedParams[1])) - return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH; - return LOCALE_FULL_MATCH; - case 3: - if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (!referenceParams[1].equals(testedParams[1])) - return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; - if (!referenceParams[2].equals(testedParams[2])) - return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; - return LOCALE_FULL_MATCH; - } - // It should be impossible to come here - return LOCALE_NO_MATCH; - } - - /** - * Return a string that represents this match level, with better matches first. - * - * The strings are sorted in lexicographic order: a better match will always be less than - * a worse match when compared together. - */ - public static String getMatchLevelSortedString(int matchLevel) { - // This works because the match levels are 0~99 (actually 0~30) - // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel - return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel); - } - - /** - * Find out whether a match level should be considered a match. - * - * This method takes a match level as returned by the #getMatchLevel method, and returns whether - * it should be considered a match in the usual sense with standard Locale functions. - * - * @param level the match level, as returned by getMatchLevel. - * @return whether this is a match or not. - */ - public static boolean isMatch(int level) { - return LOCALE_MATCH <= level; - } - - private static final HashMap<String, Locale> sLocaleCache = new HashMap<>(); - - /** - * Creates a locale from a string specification. - */ - public static Locale constructLocaleFromString(final String localeStr) { - if (localeStr == null) { - return null; - } - synchronized (sLocaleCache) { - Locale retval = sLocaleCache.get(localeStr); - if (retval != null) { - return retval; - } - String[] localeParams = localeStr.split("_", 3); - if (localeParams.length == 1) { - retval = new Locale(localeParams[0]); - } else if (localeParams.length == 2) { - retval = new Locale(localeParams[0], localeParams[1]); - } else if (localeParams.length == 3) { - retval = new Locale(localeParams[0], localeParams[1], localeParams[2]); - } - if (retval != null) { - sLocaleCache.put(localeStr, retval); - } - return retval; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java index 21daddce7..a381649a4 100644 --- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java +++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java @@ -51,6 +51,17 @@ public class RecapitalizeStatus { } } + public static String modeToString(final int recapitalizeMode) { + switch (recapitalizeMode) { + case NOT_A_RECAPITALIZE_MODE: return "undefined"; + case CAPS_MODE_ORIGINAL_MIXED_CASE: return "mixedCase"; + case CAPS_MODE_ALL_LOWER: return "allLower"; + case CAPS_MODE_FIRST_WORD_UPPER: return "firstWordUpper"; + case CAPS_MODE_ALL_UPPER: return "allUpper"; + default: return "unknown<" + recapitalizeMode + ">"; + } + } + /** * We store the location of the cursor and the string that was there before the recapitalize * action was done, and the location of the cursor and the string that was there after. diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java index 55c1dc9e5..013f024c0 100644 --- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java @@ -27,13 +27,14 @@ import android.util.Log; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodSubtype; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.common.StringUtils; -import java.util.Arrays; import java.util.HashMap; import java.util.Locale; +import javax.annotation.Nonnull; + /** * A helper class to deal with subtype locales. */ @@ -53,7 +54,6 @@ public final class SubtypeLocaleUtils { private static volatile boolean sInitialized = false; private static final Object sInitializeLock = new Object(); private static Resources sResources; - private static String[] sPredefinedKeyboardLayoutSet; // Keyboard layout to its display name map. private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>(); // Keyboard layout to subtype name resource id map. @@ -100,7 +100,6 @@ public final class SubtypeLocaleUtils { sResources = res; final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts); - sPredefinedKeyboardLayoutSet = predefinedLayoutSet; final String[] layoutDisplayNames = res.getStringArray( R.array.predefined_layout_display_names); for (int i = 0; i < predefinedLayoutSet.length; i++) { @@ -149,10 +148,6 @@ public final class SubtypeLocaleUtils { } } - public static String[] getPredefinedKeyboardLayoutSet() { - return sPredefinedKeyboardLayoutSet; - } - public static boolean isExceptionalLocale(final String localeString) { return sExceptionalLocaleToNameIdsMap.containsKey(localeString); } @@ -173,7 +168,8 @@ public final class SubtypeLocaleUtils { return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId; } - public static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) { + @Nonnull + public static Locale getDisplayLocaleOfSubtypeLocale(@Nonnull final String localeString) { if (NO_LANGUAGE.equals(localeString)) { return sResources.getConfiguration().locale; } @@ -183,17 +179,20 @@ public final class SubtypeLocaleUtils { return LocaleUtils.constructLocaleFromString(localeString); } - public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) { + public static String getSubtypeLocaleDisplayNameInSystemLocale( + @Nonnull final String localeString) { final Locale displayLocale = sResources.getConfiguration().locale; return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); } - public static String getSubtypeLocaleDisplayName(final String localeString) { + @Nonnull + public static String getSubtypeLocaleDisplayName(@Nonnull final String localeString) { final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); } - public static String getSubtypeLanguageDisplayName(final String localeString) { + @Nonnull + public static String getSubtypeLanguageDisplayName(@Nonnull final String localeString) { final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); final String languageString; if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { @@ -205,8 +204,9 @@ public final class SubtypeLocaleUtils { return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale); } - private static String getSubtypeLocaleDisplayNameInternal(final String localeString, - final Locale displayLocale) { + @Nonnull + private static String getSubtypeLocaleDisplayNameInternal(@Nonnull final String localeString, + @Nonnull final Locale displayLocale) { if (NO_LANGUAGE.equals(localeString)) { // No language subtype should be displayed in system locale. return sResources.getString(R.string.subtype_no_language); @@ -255,8 +255,9 @@ public final class SubtypeLocaleUtils { // en_US azerty T English (US) (AZERTY) exception // zz azerty T Alphabet (AZERTY) in system locale - private static String getReplacementString(final InputMethodSubtype subtype, - final Locale displayLocale) { + @Nonnull + private static String getReplacementString(@Nonnull final InputMethodSubtype subtype, + @Nonnull final Locale displayLocale) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME); @@ -264,20 +265,24 @@ public final class SubtypeLocaleUtils { return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale); } - public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) { + @Nonnull + public static String getSubtypeDisplayNameInSystemLocale( + @Nonnull final InputMethodSubtype subtype) { final Locale displayLocale = sResources.getConfiguration().locale; return getSubtypeDisplayNameInternal(subtype, displayLocale); } - public static String getSubtypeNameForLogging(final InputMethodSubtype subtype) { + @Nonnull + public static String getSubtypeNameForLogging(@Nonnull final InputMethodSubtype subtype) { if (subtype == null) { return "<null subtype>"; } return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype); } - private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype, - final Locale displayLocale) { + @Nonnull + private static String getSubtypeDisplayNameInternal(@Nonnull final InputMethodSubtype subtype, + @Nonnull final Locale displayLocale) { final String replacementString = getReplacementString(subtype, displayLocale); // TODO: rework this for multi-lingual subtypes final int nameResId = subtype.getNameResId(); @@ -302,24 +307,25 @@ public final class SubtypeLocaleUtils { getSubtypeName.runInLocale(sResources, displayLocale), displayLocale); } - public static Locale getSubtypeLocale(final InputMethodSubtype subtype) { + @Nonnull + public static Locale getSubtypeLocale(@Nonnull final InputMethodSubtype subtype) { final String localeString = subtype.getLocale(); return LocaleUtils.constructLocaleFromString(localeString); } - public static String getKeyboardLayoutSetDisplayName(final InputMethodSubtype subtype) { + @Nonnull + public static String getKeyboardLayoutSetDisplayName( + @Nonnull final InputMethodSubtype subtype) { final String layoutName = getKeyboardLayoutSetName(subtype); return getKeyboardLayoutSetDisplayName(layoutName); } - public static String getKeyboardLayoutSetDisplayName(final String layoutName) { + @Nonnull + public static String getKeyboardLayoutSetDisplayName(@Nonnull final String layoutName) { return sKeyboardLayoutToDisplayNameMap.get(layoutName); } - public static String getKeyboardLayoutSetName(final RichInputMethodSubtype subtype) { - return getKeyboardLayoutSetName(subtype.getRawSubtype()); - } - + @Nonnull public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) { String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET); if (keyboardLayoutSet == null) { @@ -339,22 +345,6 @@ public final class SubtypeLocaleUtils { return keyboardLayoutSet; } - // TODO: Get this information from the framework instead of maintaining here by ourselves. - // Sorted list of known Right-To-Left language codes. - private static final String[] SORTED_RTL_LANGUAGES = { - "ar", // Arabic - "fa", // Persian - "iw", // Hebrew - }; - static { - Arrays.sort(SORTED_RTL_LANGUAGES); - } - - public static boolean isRtlLanguage(final Locale locale) { - final String language = locale.getLanguage(); - return Arrays.binarySearch(SORTED_RTL_LANGUAGES, language) >= 0; - } - public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) { return subtype.getExtraValueOf(COMBINING_RULES); } diff --git a/native/dicttoolkit/Android.mk b/native/dicttoolkit/Android.mk new file mode 100644 index 000000000..118682dfc --- /dev/null +++ b/native/dicttoolkit/Android.mk @@ -0,0 +1,67 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifeq (,$(TARGET_BUILD_APPS)) + +# Only build if it's explicitly requested, or running mm/mmm. +ifneq ($(ONE_SHOT_MAKEFILE)$(filter $(MAKECMDGOALS),dicttoolkit),) + +# HACK: Temporarily disable host tool build on Mac until the build system is ready for C++11. +LATINIME_HOST_OSNAME := $(shell uname -s) +ifneq ($(LATINIME_HOST_OSNAME), Darwin) # TODO: Remove this + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LATIN_IME_CORE_PATH := $(LOCAL_PATH)/../jni + +LATIN_IME_DICT_TOOLKIT_SRC_DIR := src +LATIN_IME_CORE_SRC_DIR := ../jni/src + +LOCAL_CFLAGS += -Werror -Wall -Wextra -Weffc++ -Wformat=2 -Wcast-qual -Wcast-align \ + -Wwrite-strings -Wfloat-equal -Wpointer-arith -Winit-self -Wredundant-decls \ + -Woverloaded-virtual -Wsign-promo -Wno-system-headers + +# To suppress compiler warnings for unused variables/functions used for debug features etc. +LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function +LOCAL_CFLAGS += -std=c++11 -Wno-unused-parameter -Wno-unused-function + +include $(LOCAL_PATH)/NativeFileList.mk +include $(LATIN_IME_CORE_PATH)/NativeFileList.mk + +LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_DICT_TOOLKIT_SRC_DIR) \ + $(LATIN_IME_CORE_PATH)/$(LATIN_IME_CORE_SRC_DIR) + +LOCAL_SRC_FILES := $(LATIN_IME_DICT_TOOLKIT_MAIN_SRC_FILES) \ + $(addprefix $(LATIN_IME_DICT_TOOLKIT_SRC_DIR)/, $(LATIN_IME_DICT_TOOLKIT_SRC_FILES)) \ + $(addprefix $(LATIN_IME_CORE_SRC_DIR)/, $(LATIN_IME_CORE_SRC_FILES)) + +LOCAL_MODULE := dicttoolkit +LOCAL_MODULE_TAGS := optional + +LOCAL_CLANG := true +LOCAL_CXX_STL := libc++ + +include $(BUILD_HOST_EXECUTABLE) +#################### Clean up the tmp vars +include $(LOCAL_PATH)/CleanupNativeFileList.mk +#################### Unit test +include $(LOCAL_PATH)/UnitTests.mk + +endif # Darwin - TODO: Remove this + +endif + +endif # TARGET_BUILD_APPS diff --git a/native/dicttoolkit/CleanupNativeFileList.mk b/native/dicttoolkit/CleanupNativeFileList.mk new file mode 100644 index 000000000..b804b41ed --- /dev/null +++ b/native/dicttoolkit/CleanupNativeFileList.mk @@ -0,0 +1,17 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LATIN_IME_DICT_TOOLKIT_MAIN_SRC_FILES := +LATIN_IME_DICT_TOOLKIT_SRC_FILES := +LATIN_IME_DICT_TOOLKIT_TEST_FILES := diff --git a/native/dicttoolkit/NativeFileList.mk b/native/dicttoolkit/NativeFileList.mk new file mode 100644 index 000000000..d2c8c3a2c --- /dev/null +++ b/native/dicttoolkit/NativeFileList.mk @@ -0,0 +1,43 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LATIN_IME_DICT_TOOLKIT_MAIN_SRC_FILES := \ + dict_toolkit_main.cpp + +LATIN_IME_DICT_TOOLKIT_SRC_FILES := \ + $(addprefix command_executors/, \ + diff_executor.cpp \ + header_executor.cpp \ + help_executor.cpp \ + info_executor.cpp \ + makedict_executor.cpp) \ + $(addprefix offdevice_intermediate_dict/, \ + offdevice_intermediate_dict.cpp) \ + $(addprefix utils/, \ + arguments_parser.cpp \ + command_utils.cpp \ + utf8_utils.cpp) + +LATIN_IME_DICT_TOOLKIT_TEST_FILES := \ + $(addprefix command_executors/, \ + diff_executor_test.cpp \ + header_executor_test.cpp \ + info_executor_test.cpp \ + makedict_executor_test.cpp) \ + dict_toolkit_defines_test.cpp \ + $(addprefix offdevice_intermediate_dict/, \ + offdevice_intermediate_dict_test.cpp) \ + $(addprefix utils/, \ + command_utils_test.cpp \ + utf8_utils_test.cpp) diff --git a/native/dicttoolkit/UnitTests.mk b/native/dicttoolkit/UnitTests.mk new file mode 100644 index 000000000..96e28730e --- /dev/null +++ b/native/dicttoolkit/UnitTests.mk @@ -0,0 +1,69 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifeq (,$(TARGET_BUILD_APPS)) + +LOCAL_PATH := $(call my-dir) + +###################################### +include $(CLEAR_VARS) + +LATIN_IME_CORE_PATH := $(LOCAL_PATH)/../jni + +LATIN_IME_DICT_TOOLKIT_SRC_DIR := src +LATIN_IME_CORE_SRC_DIR := ../jni/src +LATIN_DICT_TOOLKIT_TEST_SRC_DIR := tests + +include $(LOCAL_PATH)/NativeFileList.mk +include $(LATIN_IME_CORE_PATH)/NativeFileList.mk + +# TODO: Remove -std=c++11 once it is set by default on host build. +LATIN_IME_SRC_DIR := src +LOCAL_ADDRESS_SANITIZER := true +LOCAL_CFLAGS += -std=c++11 -Wno-unused-parameter -Wno-unused-function +LOCAL_CLANG := true +LOCAL_CXX_STL := libc++ +LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_DICT_TOOLKIT_SRC_DIR) \ + $(LATIN_IME_CORE_PATH)/$(LATIN_IME_CORE_SRC_DIR) +LOCAL_MODULE := liblatinime_dicttoolkit_host_static_for_unittests +LOCAL_MODULE_TAGS := optional +LOCAL_SRC_FILES := \ + $(addprefix $(LATIN_IME_DICT_TOOLKIT_SRC_DIR)/, $(LATIN_IME_DICT_TOOLKIT_SRC_FILES)) \ + $(addprefix $(LATIN_IME_CORE_SRC_DIR)/, $(LATIN_IME_CORE_SRC_FILES)) +include $(BUILD_HOST_STATIC_LIBRARY) + +include $(CLEAR_VARS) + +# TODO: Remove -std=c++11 once it is set by default on host build. +LOCAL_ADDRESS_SANITIZER := true +LOCAL_CFLAGS += -std=c++11 -Wno-unused-parameter -Wno-unused-function +LOCAL_CLANG := true +LOCAL_CXX_STL := libc++ +LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_DICT_TOOLKIT_SRC_DIR) \ + $(LATIN_IME_CORE_PATH)/$(LATIN_IME_CORE_SRC_DIR) +LOCAL_MODULE := dicttoolkit_unittests +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := \ + $(addprefix $(LATIN_DICT_TOOLKIT_TEST_SRC_DIR)/, $(LATIN_IME_DICT_TOOLKIT_TEST_FILES)) +LOCAL_STATIC_LIBRARIES += liblatinime_dicttoolkit_host_static_for_unittests +include $(BUILD_HOST_NATIVE_TEST) + +include $(LOCAL_PATH)/CleanupNativeFileList.mk + +#################### Clean up the tmp vars +LATINIME_HOST_OSNAME := +LATIN_IME_SRC_DIR := +LATIN_IME_TEST_SRC_DIR := + +endif # TARGET_BUILD_APPS diff --git a/native/dicttoolkit/dict_toolkit_main.cpp b/native/dicttoolkit/dict_toolkit_main.cpp new file mode 100644 index 000000000..53cc5e915 --- /dev/null +++ b/native/dicttoolkit/dict_toolkit_main.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdio> + +#include "dict_toolkit_defines.h" +#include "utils/command_utils.h" + +void usage(int argc, char **argv) { + fprintf(stderr, "Usage: %s <command> [arguments]\n", argc > 0 ? argv[0] : "dicttoolkit"); +} + +int main(int argc, char **argv) { + if (argc < MIN_ARG_COUNT) { + usage(argc, argv); + return 1; + } + using namespace latinime::dicttoolkit; + const CommandType commandType = CommandUtils::getCommandType(argv[1]); + if (commandType == CommandType::Unknown) { + CommandUtils::printCommandUnknownMessage(argv[0], argv[1]); + return 1; + } + const auto executor = CommandUtils::getCommandExecutor(commandType); + return executor(argc - 1, argv + 1); +} diff --git a/native/dicttoolkit/run_tests.sh b/native/dicttoolkit/run_tests.sh new file mode 100755 index 000000000..44c99c144 --- /dev/null +++ b/native/dicttoolkit/run_tests.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Copyright 2014, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# check script arguments +if [[ $(type -t mmm) != function ]]; then +if [[ ${BASH_SOURCE[0]} != $0 ]]; then return; else exit 1; fi +fi + +# Host build is never supported in unbundled (NDK/tapas) build +if [[ -n $TARGET_BUILD_APPS ]]; then + echo "Host build is never supported in tapas build." 1>&2 + echo "Use lunch command instead." 1>&2 + if [[ ${BASH_SOURCE[0]} != $0 ]]; then return; else exit 1; fi +fi + +test_name=dicttoolkit_unittests + +pushd $PWD > /dev/null +cd $(gettop) +(mmm -j16 packages/inputmethods/LatinIME/native/dicttoolkit) || (make -j16 $test_name) +$ANDROID_HOST_OUT/bin/$test_name +popd > /dev/null diff --git a/native/dicttoolkit/src/command_executors/diff_executor.cpp b/native/dicttoolkit/src/command_executors/diff_executor.cpp new file mode 100644 index 000000000..bf6830686 --- /dev/null +++ b/native/dicttoolkit/src/command_executors/diff_executor.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_executors/diff_executor.h" + +#include <cstdio> + +namespace latinime { +namespace dicttoolkit { + +const char *const DiffExecutor::COMMAND_NAME = "diff"; + +/* static */ int DiffExecutor::run(const int argc, char **argv) { + fprintf(stderr, "Command '%s' has not been implemented yet.\n", COMMAND_NAME); + return 0; +} + +/* static */ void DiffExecutor::printUsage() { + printf("*** %s\n", COMMAND_NAME); + getArgumentsParser().printUsage(COMMAND_NAME, "Shows differences between two dictionaries."); +} + +/* static */ const ArgumentsParser DiffExecutor::getArgumentsParser() { + std::unordered_map<std::string, OptionSpec> optionSpecs; + optionSpecs["p"] = OptionSpec::switchOption("(plumbing) produce output suitable for a script"); + + const std::vector<ArgumentSpec> argumentSpecs = { + ArgumentSpec::singleArgument("dict1", "dictionary file"), + ArgumentSpec::singleArgument("dict2", "dictionary file") + }; + + return ArgumentsParser(std::move(optionSpecs), std::move(argumentSpecs)); +} + +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/src/command_executors/diff_executor.h b/native/dicttoolkit/src/command_executors/diff_executor.h new file mode 100644 index 000000000..f92ae49d5 --- /dev/null +++ b/native/dicttoolkit/src/command_executors/diff_executor.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_DIFF_EXECUTOR_H +#define LATINIME_DICT_TOOLKIT_DIFF_EXECUTOR_H + +#include "dict_toolkit_defines.h" +#include "utils/arguments_parser.h" + +namespace latinime { +namespace dicttoolkit { + +class DiffExecutor final { + public: + static const char *const COMMAND_NAME; + + static int run(const int argc, char **argv); + static void printUsage(); + static const ArgumentsParser getArgumentsParser(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(DiffExecutor); +}; + +} // namespace dicttoolkit +} // namepsace latinime +#endif // LATINIME_DICT_TOOLKIT_DIFF_EXECUTOR_H diff --git a/native/dicttoolkit/src/command_executors/header_executor.cpp b/native/dicttoolkit/src/command_executors/header_executor.cpp new file mode 100644 index 000000000..b3d273b4e --- /dev/null +++ b/native/dicttoolkit/src/command_executors/header_executor.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_executors/header_executor.h" + +#include <cstdio> + +namespace latinime { +namespace dicttoolkit { + +const char *const HeaderExecutor::COMMAND_NAME = "header"; + +/* static */ int HeaderExecutor::run(const int argc, char **argv) { + fprintf(stderr, "Command '%s' has not been implemented yet.\n", COMMAND_NAME); + return 0; +} + +/* static */ void HeaderExecutor::printUsage() { + printf("*** %s\n", COMMAND_NAME); + getArgumentsParser().printUsage(COMMAND_NAME, + "Prints the header contents of a dictionary file."); +} + +/* static */ const ArgumentsParser HeaderExecutor::getArgumentsParser() { + std::unordered_map<std::string, OptionSpec> optionSpecs; + optionSpecs["p"] = OptionSpec::switchOption("(plumbing) produce output suitable for a script"); + + const std::vector<ArgumentSpec> argumentSpecs = { + ArgumentSpec::singleArgument("dict", "prints the header contents of a dictionary file") + }; + + return ArgumentsParser(std::move(optionSpecs), std::move(argumentSpecs)); +} +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/src/command_executors/header_executor.h b/native/dicttoolkit/src/command_executors/header_executor.h new file mode 100644 index 000000000..44cc9cfc4 --- /dev/null +++ b/native/dicttoolkit/src/command_executors/header_executor.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_HEADER_EXECUTOR_H +#define LATINIME_DICT_TOOLKIT_HEADER_EXECUTOR_H + +#include "dict_toolkit_defines.h" +#include "utils/arguments_parser.h" + +namespace latinime { +namespace dicttoolkit { + +class HeaderExecutor final { + public: + static const char *const COMMAND_NAME; + + static int run(const int argc, char **argv); + static void printUsage(); + static const ArgumentsParser getArgumentsParser(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(HeaderExecutor); +}; + +} // namespace dicttoolkit +} // namepsace latinime +#endif // LATINIME_DICT_TOOLKIT_HEADER_EXECUTOR_H diff --git a/native/dicttoolkit/src/command_executors/help_executor.cpp b/native/dicttoolkit/src/command_executors/help_executor.cpp new file mode 100644 index 000000000..bd29a5b16 --- /dev/null +++ b/native/dicttoolkit/src/command_executors/help_executor.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_executors/help_executor.h" + +#include <cstdio> +#include <functional> +#include <vector> + +#include "command_executors/diff_executor.h" +#include "command_executors/header_executor.h" +#include "command_executors/info_executor.h" +#include "command_executors/makedict_executor.h" +#include "utils/command_utils.h" + +namespace latinime { +namespace dicttoolkit { + +const char *const HelpExecutor::COMMAND_NAME = "help"; + +/* static */ int HelpExecutor::run(const int argc, char **argv) { + printf("Available commands:\n\n"); + const std::vector<std::function<void(void)>> printUsageMethods = {DiffExecutor::printUsage, + HeaderExecutor::printUsage, InfoExecutor::printUsage, MakedictExecutor::printUsage, + printUsage}; + for (const auto &printUsageMethod : printUsageMethods) { + printUsageMethod(); + } + return 0; +} + +/* static */ void HelpExecutor::printUsage() { + printf("*** %s\n", COMMAND_NAME); + printf("Usage: %s\n", COMMAND_NAME); + printf("Show this help list.\n\n"); +} + +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/src/command_executors/help_executor.h b/native/dicttoolkit/src/command_executors/help_executor.h new file mode 100644 index 000000000..280610eb9 --- /dev/null +++ b/native/dicttoolkit/src/command_executors/help_executor.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_HELP_EXECUTOR_H +#define LATINIME_DICT_TOOLKIT_HELP_EXECUTOR_H + +#include "dict_toolkit_defines.h" + +namespace latinime { +namespace dicttoolkit { + +class HelpExecutor final { + public: + static const char *const COMMAND_NAME; + + static int run(const int argc, char **argv); + static void printUsage(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(HelpExecutor); +}; + +} // namespace dicttoolkit +} // namepsace latinime +#endif // LATINIME_DICT_TOOLKIT_HELP_EXECUTOR_H diff --git a/native/dicttoolkit/src/command_executors/info_executor.cpp b/native/dicttoolkit/src/command_executors/info_executor.cpp new file mode 100644 index 000000000..351da4aff --- /dev/null +++ b/native/dicttoolkit/src/command_executors/info_executor.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_executors/info_executor.h" + +#include <cstdio> +#include <string> +#include <unordered_map> +#include <vector> + +namespace latinime { +namespace dicttoolkit { + +const char *const InfoExecutor::COMMAND_NAME = "info"; + +/* static */ int InfoExecutor::run(const int argc, char **argv) { + fprintf(stderr, "Command '%s' has not been implemented yet.\n", COMMAND_NAME); + return 0; +} + +/* static */ void InfoExecutor::printUsage() { + printf("*** %s\n", COMMAND_NAME); + getArgumentsParser().printUsage(COMMAND_NAME, + "Prints various information about a dictionary file."); +} + +/* static */const ArgumentsParser InfoExecutor::getArgumentsParser() { + std::unordered_map<std::string, OptionSpec> optionSpecs; + optionSpecs["p"] = OptionSpec::switchOption("(plumbing) produce output suitable for a script"); + + const std::vector<ArgumentSpec> argumentSpecs = { + ArgumentSpec::singleArgument("dict", "dictionary file name"), + ArgumentSpec::variableLengthArguments("word", 0 /* minCount */, + ArgumentSpec::UNLIMITED_COUNT, "word to show information") + }; + + return ArgumentsParser(std::move(optionSpecs), std::move(argumentSpecs)); +} + +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/src/command_executors/info_executor.h b/native/dicttoolkit/src/command_executors/info_executor.h new file mode 100644 index 000000000..d4106d59f --- /dev/null +++ b/native/dicttoolkit/src/command_executors/info_executor.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_INFO_EXECUTOR_H +#define LATINIME_DICT_TOOLKIT_INFO_EXECUTOR_H + +#include "dict_toolkit_defines.h" +#include "utils/arguments_parser.h" + +namespace latinime { +namespace dicttoolkit { + +class InfoExecutor final { + public: + static const char *const COMMAND_NAME; + + static int run(const int argc, char **argv); + static void printUsage(); + static const ArgumentsParser getArgumentsParser(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(InfoExecutor); +}; + +} // namepsace dicttoolkit +} // namespace latinime +#endif // LATINIME_DICT_TOOLKIT_INFO_EXECUTOR_H diff --git a/native/dicttoolkit/src/command_executors/makedict_executor.cpp b/native/dicttoolkit/src/command_executors/makedict_executor.cpp new file mode 100644 index 000000000..8a84e8069 --- /dev/null +++ b/native/dicttoolkit/src/command_executors/makedict_executor.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_executors/makedict_executor.h" + +#include <cstdio> + +namespace latinime { +namespace dicttoolkit { + +const char *const MakedictExecutor::COMMAND_NAME = "makedict"; + +/* static */ int MakedictExecutor::run(const int argc, char **argv) { + fprintf(stderr, "Command '%s' has not been implemented yet.\n", COMMAND_NAME); + return 0; +} + +/* static */ void MakedictExecutor::printUsage() { + printf("*** %s\n", COMMAND_NAME); + getArgumentsParser().printUsage(COMMAND_NAME, + "Converts a source dictionary file to one or several outputs.\n" + "Source can be a binary dictionary file or a combined format file.\n" + "Binary version 2 (Jelly Bean), 4, and combined format outputs are supported."); +} + +/* static */const ArgumentsParser MakedictExecutor::getArgumentsParser() { + std::unordered_map<std::string, OptionSpec> optionSpecs; + optionSpecs["o"] = OptionSpec::keyValueOption("format", "2", + "output format version: 2/4/combined"); + optionSpecs["t"] = OptionSpec::keyValueOption("mode", "off", + "code point table switch: on/off/auto"); + + const std::vector<ArgumentSpec> argumentSpecs = { + ArgumentSpec::singleArgument("src_dict", "source dictionary file"), + ArgumentSpec::singleArgument("dest_dict", "output dictionary file") + }; + + return ArgumentsParser(std::move(optionSpecs), std::move(argumentSpecs)); +} + +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/src/command_executors/makedict_executor.h b/native/dicttoolkit/src/command_executors/makedict_executor.h new file mode 100644 index 000000000..c3de977a3 --- /dev/null +++ b/native/dicttoolkit/src/command_executors/makedict_executor.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_MAKEDICT_EXECUTOR_H +#define LATINIME_DICT_TOOLKIT_MAKEDICT_EXECUTOR_H + +#include "dict_toolkit_defines.h" +#include "utils/arguments_parser.h" + +namespace latinime { +namespace dicttoolkit { + +class MakedictExecutor final { + public: + static const char *const COMMAND_NAME; + + static int run(const int argc, char **argv); + static void printUsage(); + static const ArgumentsParser getArgumentsParser(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(MakedictExecutor); +}; + +} // namespace dicttoolkit +} // namepsace latinime +#endif // LATINIME_DICT_TOOLKIT_MAKEDICT_EXECUTOR_H diff --git a/native/dicttoolkit/src/dict_toolkit_defines.h b/native/dicttoolkit/src/dict_toolkit_defines.h new file mode 100644 index 000000000..dbaae0ca0 --- /dev/null +++ b/native/dicttoolkit/src/dict_toolkit_defines.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_DEFINES_H +#define LATINIME_DICT_TOOLKIT_DEFINES_H + +#include "defines.h" + +#define MIN_ARG_COUNT 2 + +#endif // LATINIME_DICT_TOOLKIT_DEFINES_H diff --git a/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict.cpp b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict.cpp new file mode 100644 index 000000000..af28131cf --- /dev/null +++ b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "offdevice_intermediate_dict/offdevice_intermediate_dict.h" + +#include "offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node.h" + +namespace latinime { +namespace dicttoolkit { + +bool OffdeviceIntermediateDict::addWord(const WordProperty &wordProperty) { + const CodePointArrayView codePoints = wordProperty.getCodePoints(); + if (codePoints.empty() || codePoints.size() > MAX_WORD_LENGTH) { + return false; + } + return addWordInner(codePoints, wordProperty, mRootPtNodeArray); +} + +bool OffdeviceIntermediateDict::addWordInner(const CodePointArrayView codePoints, + const WordProperty &wordProperty, OffdeviceIntermediateDictPtNodeArray &ptNodeArray) { + auto ptNodeList = ptNodeArray.getMutablePtNodeList(); + auto ptNodeIt = ptNodeList->begin(); + for (; ptNodeIt != ptNodeList->end(); ++ptNodeIt) { + const auto &ptNode = *ptNodeIt; + const CodePointArrayView ptNodeCodePoints = ptNode->getPtNodeCodePoints(); + if (codePoints[0] < ptNodeCodePoints[0]) { + continue; + } + if (codePoints[0] > ptNodeCodePoints[0]) { + break; + } + size_t i = 1; + for (; i < codePoints.size(); ++i) { + if (i >= ptNodeCodePoints.size()) { + // Add new child. + return addWordInner(codePoints.skip(i), wordProperty, + ptNode->getChildrenPtNodeArray()); + } + if (codePoints[i] != ptNodeCodePoints[i]) { + break; + } + } + if (codePoints.size() == i && codePoints.size() == ptNodeCodePoints.size()) { + // All code points matched. + if (ptNode->getWordProperty()) { + // Adding the same word multiple times is not supported. + return false; + } + ptNodeList->insert(ptNodeIt, + std::make_shared<OffdeviceIntermediateDictPtNode>(wordProperty, *ptNode)); + ptNodeList->erase(ptNodeIt); + return true; + } + // The (i+1)-th elements are different. + // Create and Add new parent ptNode for the common part. + auto newPtNode = codePoints.size() == i + ? std::make_shared<OffdeviceIntermediateDictPtNode>(codePoints, wordProperty) + : std::make_shared<OffdeviceIntermediateDictPtNode>(codePoints.limit(i)); + ptNodeList->insert(ptNodeIt, newPtNode); + OffdeviceIntermediateDictPtNodeArray &childrenPtNodeArray = + newPtNode->getChildrenPtNodeArray(); + // Add new child for the existing ptNode. + childrenPtNodeArray.getMutablePtNodeList()->push_back( + std::make_shared<OffdeviceIntermediateDictPtNode>( + ptNodeCodePoints.skip(i), *ptNode)); + ptNodeList->erase(ptNodeIt); + if (codePoints.size() != i) { + // Add a child for the new word. + return addWordInner(codePoints.skip(i), wordProperty, childrenPtNodeArray); + } + return true; + } + ptNodeList->insert(ptNodeIt, + std::make_shared<OffdeviceIntermediateDictPtNode>(codePoints, wordProperty)); + return true; +} + +const WordProperty *OffdeviceIntermediateDict::getWordProperty( + const CodePointArrayView codePoints) const { + const OffdeviceIntermediateDictPtNodeArray *ptNodeArray = &mRootPtNodeArray; + for (size_t i = 0; i < codePoints.size();) { + bool foundNext = false; + for (const auto ptNode : ptNodeArray->getPtNodeList()) { + const CodePointArrayView ptNodeCodePoints = ptNode->getPtNodeCodePoints(); + if (codePoints[i] < ptNodeCodePoints[0]) { + continue; + } + if (codePoints[i] > ptNodeCodePoints[0] + || codePoints.size() < ptNodeCodePoints.size()) { + return nullptr; + } + for (size_t j = 1; j < ptNodeCodePoints.size(); ++j) { + if (codePoints[i + j] != ptNodeCodePoints[j]) { + return nullptr; + } + } + i += ptNodeCodePoints.size(); + if (i == codePoints.size()) { + return ptNode->getWordProperty(); + } + ptNodeArray = &ptNode->getChildrenPtNodeArray(); + foundNext = true; + break; + } + if (!foundNext) { + break; + } + } + return nullptr; +} + +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict.h b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict.h new file mode 100644 index 000000000..13d26ba91 --- /dev/null +++ b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_H +#define LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_H + +#include "dict_toolkit_defines.h" +#include "offdevice_intermediate_dict/offdevice_intermediate_dict_header.h" +#include "offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node_array.h" +#include "suggest/core/dictionary/property/word_property.h" +#include "utils/int_array_view.h" + +namespace latinime { +namespace dicttoolkit { + +/** + * On memory patricia trie to represent a dictionary. + */ +class OffdeviceIntermediateDict final { + public: + OffdeviceIntermediateDict(const OffdeviceIntermediateDictHeader &header) + : mHeader(header), mRootPtNodeArray() {} + + bool addWord(const WordProperty &wordProperty); + // The returned value will be invalid after modifying the dictionary. e.g. calling addWord(). + const WordProperty *getWordProperty(const CodePointArrayView codePoints) const; + const OffdeviceIntermediateDictHeader &getHeader() const { return mHeader; } + + private: + DISALLOW_ASSIGNMENT_OPERATOR(OffdeviceIntermediateDict); + + const OffdeviceIntermediateDictHeader mHeader; + OffdeviceIntermediateDictPtNodeArray mRootPtNodeArray; + + bool addWordInner(const CodePointArrayView codePoints, const WordProperty &wordProperty, + OffdeviceIntermediateDictPtNodeArray &ptNodeArray); +}; + +} // namespace dicttoolkit +} // namespace latinime +#endif // LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_H diff --git a/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict_header.h b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict_header.h new file mode 100644 index 000000000..440627a79 --- /dev/null +++ b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict_header.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_HEADER_H +#define LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_HEADER_H + +#include <map> +#include <vector> + +#include "dict_toolkit_defines.h" + +namespace latinime { +namespace dicttoolkit { + +class OffdeviceIntermediateDictHeader final { + public: + using AttributeMap = std::map<std::vector<int>, std::vector<int>>; + + OffdeviceIntermediateDictHeader(const AttributeMap &attributesMap) + : mAttributeMap(attributesMap) {} + + private: + DISALLOW_DEFAULT_CONSTRUCTOR(OffdeviceIntermediateDictHeader); + DISALLOW_ASSIGNMENT_OPERATOR(OffdeviceIntermediateDictHeader); + + const AttributeMap mAttributeMap; +}; + +} // namespace dicttoolkit +} // namespace latinime +#endif // LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_HEADER_H diff --git a/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node.h b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node.h new file mode 100644 index 000000000..721ccd778 --- /dev/null +++ b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_PT_NODE_H +#define LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_PT_NODE_H + +#include <memory> + +#include "dict_toolkit_defines.h" +#include "offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node_array.h" +#include "suggest/core/dictionary/property/word_property.h" +#include "utils/int_array_view.h" + +namespace latinime { +namespace dicttoolkit { + +class OffdeviceIntermediateDictPtNode final { + public: + // Non-terminal + OffdeviceIntermediateDictPtNode(const CodePointArrayView ptNodeCodePoints) + : mPtNodeCodePoints(ptNodeCodePoints.toVector()), mChildrenPtNodeArray(), + mWortProperty(nullptr) {} + + // Terminal + OffdeviceIntermediateDictPtNode(const CodePointArrayView ptNodeCodePoints, + const WordProperty &wordProperty) + : mPtNodeCodePoints(ptNodeCodePoints.toVector()), mChildrenPtNodeArray(), + mWortProperty(new WordProperty(wordProperty)) {} + + // Replacing PtNodeCodePoints. + OffdeviceIntermediateDictPtNode(const CodePointArrayView ptNodeCodePoints, + const OffdeviceIntermediateDictPtNode &ptNode) + : mPtNodeCodePoints(ptNodeCodePoints.toVector()), + mChildrenPtNodeArray(ptNode.mChildrenPtNodeArray), + mWortProperty(new WordProperty(*ptNode.mWortProperty)) {} + + // Replacing WordProperty. + OffdeviceIntermediateDictPtNode(const WordProperty &wordProperty, + const OffdeviceIntermediateDictPtNode &ptNode) + : mPtNodeCodePoints(ptNode.mPtNodeCodePoints), + mChildrenPtNodeArray(ptNode.mChildrenPtNodeArray), + mWortProperty(new WordProperty(wordProperty)) {} + + const WordProperty *getWordProperty() const { + return mWortProperty.get(); + } + + const CodePointArrayView getPtNodeCodePoints() const { + return CodePointArrayView(mPtNodeCodePoints); + } + + OffdeviceIntermediateDictPtNodeArray &getChildrenPtNodeArray() { + return mChildrenPtNodeArray; + } + + private: + DISALLOW_COPY_AND_ASSIGN(OffdeviceIntermediateDictPtNode); + + const std::vector<int> mPtNodeCodePoints; + OffdeviceIntermediateDictPtNodeArray mChildrenPtNodeArray; + const std::unique_ptr<WordProperty> mWortProperty; +}; + +} // namespace dicttoolkit +} // namespace latinime +#endif // LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_PT_NODE_H diff --git a/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node_array.h b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node_array.h new file mode 100644 index 000000000..f87456ce0 --- /dev/null +++ b/native/dicttoolkit/src/offdevice_intermediate_dict/offdevice_intermediate_dict_pt_node_array.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_PT_NODE_ARRAY_H +#define LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_PT_NODE_ARRAY_H + +#include <list> +#include <memory> + +#include "dict_toolkit_defines.h" + +namespace latinime { +namespace dicttoolkit { + +class OffdeviceIntermediateDictPtNode; + +class OffdeviceIntermediateDictPtNodeArray final { + public: + const std::list<std::shared_ptr<OffdeviceIntermediateDictPtNode>> &getPtNodeList() const { + return mPtNodes; + } + + std::list<std::shared_ptr<OffdeviceIntermediateDictPtNode>> *getMutablePtNodeList() { + return &mPtNodes; + } + + private: + DISALLOW_ASSIGNMENT_OPERATOR(OffdeviceIntermediateDictPtNodeArray); + + std::list<std::shared_ptr<OffdeviceIntermediateDictPtNode>> mPtNodes; +}; + +} // namespace dicttoolkit +} // namespace latinime +#endif // LATINIME_DICT_TOOLKIT_OFFDEVICE_INTERMEDIATE_DICT_PT_NODE_ARRAY_H diff --git a/native/dicttoolkit/src/utils/arguments_and_options.h b/native/dicttoolkit/src/utils/arguments_and_options.h new file mode 100644 index 000000000..d8f5985e5 --- /dev/null +++ b/native/dicttoolkit/src/utils/arguments_and_options.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_ARGUMENTS_AND_OPTIONS_H +#define LATINIME_DICT_TOOLKIT_ARGUMENTS_AND_OPTIONS_H + +#include <string> +#include <unordered_map> +#include <vector> + +#include "dict_toolkit_defines.h" + +namespace latinime { +namespace dicttoolkit { + +class ArgumentsAndOptions { + public: + ArgumentsAndOptions() : mIsValid(false), mOptions(), mArguments() {} + + ArgumentsAndOptions(std::unordered_map<std::string, std::string> &&options, + std::unordered_map<std::string, std::vector<std::string>> &&arguments) + : mIsValid(true), mOptions(std::move(options)), mArguments(std::move(arguments)) {} + + bool isValid() const { + return mIsValid; + } + + bool hasOption(const std::string &optionName) const { + return mOptions.find(optionName) != mOptions.end(); + } + + private: + DISALLOW_ASSIGNMENT_OPERATOR(ArgumentsAndOptions); + + const bool mIsValid; + const std::unordered_map<std::string, std::string> mOptions; + const std::unordered_map<std::string, std::vector<std::string>> mArguments; +}; +} // namespace dicttoolkit +} // namespace latinime +#endif // LATINIME_DICT_TOOLKIT_ARGUMENTS_AND_OPTIONS_H diff --git a/native/dicttoolkit/src/utils/arguments_parser.cpp b/native/dicttoolkit/src/utils/arguments_parser.cpp new file mode 100644 index 000000000..039dae35b --- /dev/null +++ b/native/dicttoolkit/src/utils/arguments_parser.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils/arguments_parser.h" + +namespace latinime { +namespace dicttoolkit { + +const int ArgumentSpec::UNLIMITED_COUNT = -1; + +bool ArgumentsParser::validateSpecs() const { + for (size_t i = 0; i < mArgumentSpecs.size() ; ++i) { + if (mArgumentSpecs[i].getMinCount() != mArgumentSpecs[i].getMaxCount() + && i != mArgumentSpecs.size() - 1) { + AKLOGE("Variable length argument must be at the end."); + return false; + } + } + return true; +} + +void ArgumentsParser::printUsage(const std::string &commandName, + const std::string &description) const { + printf("Usage: %s", commandName.c_str()); + for (const auto &option : mOptionSpecs) { + const std::string &optionName = option.first; + const OptionSpec &spec = option.second; + printf(" [-%s", optionName.c_str()); + if (spec.takeValue()) { + printf(" <%s>", spec.getValueName().c_str()); + } + printf("]"); + } + for (const auto &argSpec : mArgumentSpecs) { + if (argSpec.getMinCount() == 0 && argSpec.getMaxCount() == 1) { + printf(" [<%s>]", argSpec.getName().c_str()); + } else if (argSpec.getMinCount() == 1 && argSpec.getMaxCount() == 1) { + printf(" <%s>", argSpec.getName().c_str()); + } else if (argSpec.getMinCount() == 0) { + printf(" [<%s>...]", argSpec.getName().c_str()); + } else if (argSpec.getMinCount() == 1) { + printf(" <%s>...", argSpec.getName().c_str()); + } + } + printf("\n%s\n\n", description.c_str()); + for (const auto &option : mOptionSpecs) { + const std::string &optionName = option.first; + const OptionSpec &spec = option.second; + printf(" -%s", optionName.c_str()); + if (spec.takeValue()) { + printf(" <%s>", spec.getValueName().c_str()); + } + printf("\t\t\t%s", spec.getDescription().c_str()); + if (spec.takeValue() && !spec.getDefaultValue().empty()) { + printf("\tdefault: %s", spec.getDefaultValue().c_str()); + } + printf("\n"); + } + for (const auto &argSpec : mArgumentSpecs) { + printf(" <%s>\t\t\t%s\n", argSpec.getName().c_str(), argSpec.getDescription().c_str()); + } + printf("\n\n"); +} + +const ArgumentsAndOptions ArgumentsParser::parseArguments(const int argc, char **argv) const { + // TODO: Implement + return ArgumentsAndOptions(); +} + +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/src/utils/arguments_parser.h b/native/dicttoolkit/src/utils/arguments_parser.h new file mode 100644 index 000000000..be2dd8749 --- /dev/null +++ b/native/dicttoolkit/src/utils/arguments_parser.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_ARGUMENTS_PARSER_H +#define LATINIME_DICT_TOOLKIT_ARGUMENTS_PARSER_H + +#include <string> +#include <unordered_map> +#include <vector> + +#include "dict_toolkit_defines.h" +#include "utils/arguments_and_options.h" + +namespace latinime { +namespace dicttoolkit { + +class OptionSpec { + public: + // Default constructor and assignment operator is enabled to be used with std::unordered_map. + OptionSpec() = default; + OptionSpec &operator=(const OptionSpec &) = default; + + static OptionSpec keyValueOption(const std::string &valueName, const std::string &defaultValue, + const std::string &description) { + return OptionSpec(true /* takeValue */, valueName, defaultValue, description); + } + + static OptionSpec switchOption(const std::string &description) { + return OptionSpec(false /* takeValue */, "" /* valueName */, "" /* defaultValue */, + description); + } + + bool takeValue() const { return mTakeValue; } + const std::string &getValueName() const { return mValueName; } + const std::string &getDefaultValue() const { return mDefaultValue; } + const std::string &getDescription() const { return mDescription; } + + private: + OptionSpec(const bool takeValue, const std::string &valueName, const std::string &defaultValue, + const std::string &description) + : mTakeValue(takeValue), mValueName(valueName), mDefaultValue(defaultValue), + mDescription(description) {} + + // Whether the option have to be used with a value or just a switch. + // e.g. 'f' in "command -f /path/to/file" is mTakeValue == true. + // 'f' in "command -f -t" is mTakeValue == false. + bool mTakeValue; + // Name of the value used to show usage. + std::string mValueName; + std::string mDefaultValue; + std::string mDescription; +}; + +class ArgumentSpec { + public: + static const int UNLIMITED_COUNT; + + static ArgumentSpec singleArgument(const std::string &name, const std::string &description) { + return ArgumentSpec(name, 1 /* minCount */, 1 /* maxCount */, description); + } + + static ArgumentSpec variableLengthArguments(const std::string &name, const int minCount, + const int maxCount, const std::string &description) { + return ArgumentSpec(name, minCount, maxCount, description); + } + + const std::string &getName() const { return mName; } + int getMinCount() const { return mMinCount; } + int getMaxCount() const { return mMaxCount; } + const std::string &getDescription() const { return mDescription; } + + private: + DISALLOW_DEFAULT_CONSTRUCTOR(ArgumentSpec); + + ArgumentSpec(const std::string &name, const int minCount, const int maxCount, + const std::string &description) + : mName(name), mMinCount(minCount), mMaxCount(maxCount), mDescription(description) {} + + const std::string mName; + const int mMinCount; + const int mMaxCount; + const std::string mDescription; +}; + +class ArgumentsParser { + public: + ArgumentsParser(std::unordered_map<std::string, OptionSpec> &&optionSpecs, + std::vector<ArgumentSpec> &&argumentSpecs) + : mOptionSpecs(std::move(optionSpecs)), mArgumentSpecs(std::move(argumentSpecs)) {} + + const ArgumentsAndOptions parseArguments(const int argc, char **argv) const; + bool validateSpecs() const; + void printUsage(const std::string &commandName, const std::string &description) const; + + private: + DISALLOW_DEFAULT_CONSTRUCTOR(ArgumentsParser); + DISALLOW_ASSIGNMENT_OPERATOR(ArgumentsParser); + + const std::unordered_map<std::string, OptionSpec> mOptionSpecs; + const std::vector<ArgumentSpec> mArgumentSpecs; +}; + +} // namespace dicttoolkit +} // namespace latinime +#endif // LATINIME_DICT_TOOLKIT_ARGUMENTS_PARSER_H diff --git a/native/dicttoolkit/src/utils/command_utils.cpp b/native/dicttoolkit/src/utils/command_utils.cpp new file mode 100644 index 000000000..34196425e --- /dev/null +++ b/native/dicttoolkit/src/utils/command_utils.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils/command_utils.h" + +#include <cstdio> + +#include "command_executors/diff_executor.h" +#include "command_executors/header_executor.h" +#include "command_executors/help_executor.h" +#include "command_executors/info_executor.h" +#include "command_executors/makedict_executor.h" + +namespace latinime { +namespace dicttoolkit { + +/* static */ CommandType CommandUtils::getCommandType(const std::string &commandName) { + if (commandName == InfoExecutor::COMMAND_NAME) { + return CommandType::Info; + } else if (commandName == DiffExecutor::COMMAND_NAME) { + return CommandType::Diff; + } else if (commandName == MakedictExecutor::COMMAND_NAME) { + return CommandType::Makedict; + } else if (commandName == HeaderExecutor::COMMAND_NAME) { + return CommandType::Header; + } else if (commandName == HelpExecutor::COMMAND_NAME) { + return CommandType::Help; + } else { + return CommandType::Unknown; + } +} + +/* static */ void CommandUtils::printCommandUnknownMessage(const std::string &programName, + const std::string &commandName) { + fprintf(stderr, "Command '%s' is unknown. Try '%s %s' for more information.\n", + commandName.c_str(), programName.c_str(), HelpExecutor::COMMAND_NAME); +} + +/* static */ std::function<int(int, char **)> CommandUtils::getCommandExecutor( + const CommandType commandType) { + switch (commandType) { + case CommandType::Info: + return InfoExecutor::run; + case CommandType::Diff: + return DiffExecutor::run; + case CommandType::Makedict: + return MakedictExecutor::run; + case CommandType::Header: + return HeaderExecutor::run; + case CommandType::Help: + return HelpExecutor::run; + default: + return [] (int, char **) -> int { + printf("Command executor not found."); + return 1; + }; + } +} + +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/src/utils/command_utils.h b/native/dicttoolkit/src/utils/command_utils.h new file mode 100644 index 000000000..4a181f194 --- /dev/null +++ b/native/dicttoolkit/src/utils/command_utils.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_COMMAND_UTILS_H +#define LATINIME_DICT_TOOLKIT_COMMAND_UTILS_H + +#include <functional> +#include <memory> +#include <string> + +#include "dict_toolkit_defines.h" + +namespace latinime { +namespace dicttoolkit { + +enum class CommandType : int { + Info, + Diff, + Makedict, + Header, + Help, + Unknown +}; + +class CommandUtils { +public: + static CommandType getCommandType(const std::string &commandName); + static void printCommandUnknownMessage(const std::string &programName, + const std::string &commandName); + static std::function<int(int, char **)> getCommandExecutor(const CommandType commandType); + +private: + DISALLOW_IMPLICIT_CONSTRUCTORS(CommandUtils); +}; +} // namespace dicttoolkit +} // namespace latinime +#endif // LATINIME_DICT_TOOLKIT_COMMAND_UTILS_H diff --git a/native/dicttoolkit/src/utils/utf8_utils.cpp b/native/dicttoolkit/src/utils/utf8_utils.cpp new file mode 100644 index 000000000..0f349f512 --- /dev/null +++ b/native/dicttoolkit/src/utils/utf8_utils.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils/utf8_utils.h" + +#include "utils/char_utils.h" + +namespace latinime { +namespace dicttoolkit { + +const size_t Utf8Utils::MAX_SEQUENCE_SIZE_FOR_A_CODE_POINT = 4; +const uint8_t Utf8Utils::FIRST_BYTE_MARKER_MASKS[] = {0, 0x80, 0xE0, 0xF0, 0xF8}; +const uint8_t Utf8Utils::FIRST_BYTE_MARKERS[] = {0, 0x00, 0xC0, 0xE0, 0xF0}; +const uint8_t Utf8Utils::FIRST_BYTE_CODE_POINT_BITS_MASKS[] = {0, 0x7F, 0x1F, 0x0F, 0x03}; +const int Utf8Utils::MAX_ENCODED_CODE_POINT_VALUES[] = {-1, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +const uint8_t Utf8Utils::TRAILING_BYTE_CODE_POINT_BITS_MASK = 0x3F; +const uint8_t Utf8Utils::TRAILING_BYTE_MARKER = 0x80; +const size_t Utf8Utils::CODE_POINT_BIT_COUNT_IN_TRAILING_BYTE = 6; + +/* static */ std::vector<int> Utf8Utils::getCodePoints(const std::string &utf8Str) { + std::vector<int> codePoints; + int remainingByteCountForCurrentCodePoint = 0; + int currentCodePointSequenceSize = 0; + int codePoint = 0; + for (const char c : utf8Str) { + if (remainingByteCountForCurrentCodePoint == 0) { + currentCodePointSequenceSize = getSequenceSizeByCheckingFirstByte(c); + if (currentCodePointSequenceSize <= 0) { + AKLOGE("%x is an invalid utf8 first byte value.", c); + return std::vector<int>(); + } + remainingByteCountForCurrentCodePoint = currentCodePointSequenceSize; + codePoint = maskFirstByte(c, remainingByteCountForCurrentCodePoint); + } else { + codePoint <<= CODE_POINT_BIT_COUNT_IN_TRAILING_BYTE; + codePoint += maskTrailingByte(c); + } + remainingByteCountForCurrentCodePoint--; + if (remainingByteCountForCurrentCodePoint == 0) { + if (codePoint <= MAX_ENCODED_CODE_POINT_VALUES[currentCodePointSequenceSize - 1]) { + AKLOGE("%d bytes encode for codePoint(%x) is a redundant UTF-8 sequence.", + currentCodePointSequenceSize, codePoint); + return std::vector<int>(); + } + codePoints.push_back(codePoint); + } + } + return codePoints; +} + +/* static */ int Utf8Utils::getSequenceSizeByCheckingFirstByte(const uint8_t firstByte) { + for (size_t i = 1; i <= MAX_SEQUENCE_SIZE_FOR_A_CODE_POINT; ++i) { + if ((firstByte & FIRST_BYTE_MARKER_MASKS[i]) == FIRST_BYTE_MARKERS[i]) { + return i; + } + } + // Not a valid utf8 char first byte. + return -1; +} + +/* static */ AK_FORCE_INLINE int Utf8Utils::maskFirstByte(const uint8_t firstByte, + const int sequenceSize) { + return firstByte & FIRST_BYTE_CODE_POINT_BITS_MASKS[sequenceSize]; +} + +/* static */ AK_FORCE_INLINE int Utf8Utils::maskTrailingByte(const uint8_t secondOrLaterByte) { + return secondOrLaterByte & TRAILING_BYTE_CODE_POINT_BITS_MASK; +} + +/* static */ std::string Utf8Utils::getUtf8String(const CodePointArrayView codePoints) { + std::string utf8String; + for (const int codePoint : codePoints) { + const int sequenceSize = getSequenceSizeToEncodeCodePoint(codePoint); + if (sequenceSize <= 0) { + AKLOGE("Cannot encode code point (%d).", codePoint); + return std::string(); + } + const int trailingByteCount = sequenceSize - 1; + // Output first byte. + const int value = codePoint >> (trailingByteCount * CODE_POINT_BIT_COUNT_IN_TRAILING_BYTE); + utf8String.push_back(static_cast<char>(value | FIRST_BYTE_MARKERS[sequenceSize])); + // Output second and later bytes. + for (int i = 1; i < sequenceSize; ++i) { + const int shiftAmount = (trailingByteCount - i) * CODE_POINT_BIT_COUNT_IN_TRAILING_BYTE; + const int value = (codePoint >> shiftAmount) & TRAILING_BYTE_CODE_POINT_BITS_MASK; + utf8String.push_back(static_cast<char>(value | TRAILING_BYTE_MARKER)); + } + } + return utf8String; +} + +/* static */ int Utf8Utils::getSequenceSizeToEncodeCodePoint(const int codePoint) { + if (codePoint < 0) { + return -1; + } + for (size_t i = 1; i <= MAX_SEQUENCE_SIZE_FOR_A_CODE_POINT; ++i) { + if (codePoint <= MAX_ENCODED_CODE_POINT_VALUES[i]) { + return i; + } + } + return -1; +} + +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/src/utils/utf8_utils.h b/native/dicttoolkit/src/utils/utf8_utils.h new file mode 100644 index 000000000..35818e56c --- /dev/null +++ b/native/dicttoolkit/src/utils/utf8_utils.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICT_TOOLKIT_UTF8_UTILS_H +#define LATINIME_DICT_TOOLKIT_UTF8_UTILS_H + +#include <cstdint> +#include <string> +#include <vector> + +#include "dict_toolkit_defines.h" +#include "utils/int_array_view.h" + +namespace latinime { +namespace dicttoolkit { + +class Utf8Utils { +public: + static std::vector<int> getCodePoints(const std::string &utf8Str); + static std::string getUtf8String(const CodePointArrayView codePoints); + +private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Utf8Utils); + + // Values indexed by sequence size. + static const size_t MAX_SEQUENCE_SIZE_FOR_A_CODE_POINT; + static const uint8_t FIRST_BYTE_MARKER_MASKS[]; + static const uint8_t FIRST_BYTE_MARKERS[]; + static const uint8_t FIRST_BYTE_CODE_POINT_BITS_MASKS[]; + static const int MAX_ENCODED_CODE_POINT_VALUES[]; + + static const uint8_t TRAILING_BYTE_CODE_POINT_BITS_MASK; + static const uint8_t TRAILING_BYTE_MARKER; + static const size_t CODE_POINT_BIT_COUNT_IN_TRAILING_BYTE; + + static int getSequenceSizeByCheckingFirstByte(const uint8_t firstByte); + static int maskFirstByte(const uint8_t firstByte, const int encodeSize); + static int maskTrailingByte(const uint8_t secondOrLaterByte); + static int getSequenceSizeToEncodeCodePoint(const int codePoint); +}; +} // namespace dicttoolkit +} // namespace latinime +#endif // LATINIME_DICT_TOOLKIT_UTF8_UTILS_H diff --git a/native/dicttoolkit/tests/command_executors/diff_executor_test.cpp b/native/dicttoolkit/tests/command_executors/diff_executor_test.cpp new file mode 100644 index 000000000..444141427 --- /dev/null +++ b/native/dicttoolkit/tests/command_executors/diff_executor_test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_executors/diff_executor.h" + +#include <gtest/gtest.h> + +namespace latinime { +namespace dicttoolkit { +namespace { + +TEST(DiffExecutorTests, TestArguemntSpecs) { + EXPECT_TRUE(DiffExecutor::getArgumentsParser().validateSpecs()); +} + +} // namespace +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/tests/command_executors/header_executor_test.cpp b/native/dicttoolkit/tests/command_executors/header_executor_test.cpp new file mode 100644 index 000000000..a94150b01 --- /dev/null +++ b/native/dicttoolkit/tests/command_executors/header_executor_test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_executors/header_executor.h" + +#include <gtest/gtest.h> + +namespace latinime { +namespace dicttoolkit { +namespace { + +TEST(HeaderExecutorTests, TestArguemntSpecs) { + EXPECT_TRUE(HeaderExecutor::getArgumentsParser().validateSpecs()); +} + +} // namespace +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/tests/command_executors/info_executor_test.cpp b/native/dicttoolkit/tests/command_executors/info_executor_test.cpp new file mode 100644 index 000000000..debe8c601 --- /dev/null +++ b/native/dicttoolkit/tests/command_executors/info_executor_test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_executors/info_executor.h" + +#include <gtest/gtest.h> + +namespace latinime { +namespace dicttoolkit { +namespace { + +TEST(InfoExecutorTests, TestArguemntSpecs) { + EXPECT_TRUE(InfoExecutor::getArgumentsParser().validateSpecs()); +} + +} // namespace +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/tests/command_executors/makedict_executor_test.cpp b/native/dicttoolkit/tests/command_executors/makedict_executor_test.cpp new file mode 100644 index 000000000..44eb3dc1b --- /dev/null +++ b/native/dicttoolkit/tests/command_executors/makedict_executor_test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_executors/makedict_executor.h" + +#include <gtest/gtest.h> + +namespace latinime { +namespace dicttoolkit { +namespace { + +TEST(MakedictExecutorTests, TestArguemntSpecs) { + EXPECT_TRUE(MakedictExecutor::getArgumentsParser().validateSpecs()); +} + +} // namespace +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/tests/dict_toolkit_defines_test.cpp b/native/dicttoolkit/tests/dict_toolkit_defines_test.cpp new file mode 100644 index 000000000..3445bd0c5 --- /dev/null +++ b/native/dicttoolkit/tests/dict_toolkit_defines_test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dict_toolkit_defines.h" + +#include <gtest/gtest.h> + +namespace latinime { +namespace dicttoolkit { +namespace { + +// Initial trivial test case. +TEST(DictToolkitDefinesTest, TestKeycodeSpace) { + EXPECT_EQ(' ', KEYCODE_SPACE); +} + +} // namespace +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/tests/offdevice_intermediate_dict/offdevice_intermediate_dict_test.cpp b/native/dicttoolkit/tests/offdevice_intermediate_dict/offdevice_intermediate_dict_test.cpp new file mode 100644 index 000000000..f2e24ab5f --- /dev/null +++ b/native/dicttoolkit/tests/offdevice_intermediate_dict/offdevice_intermediate_dict_test.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "offdevice_intermediate_dict/offdevice_intermediate_dict.h" + +#include <gtest/gtest.h> + +#include <vector> + +#include "suggest/core/dictionary/property/word_property.h" +#include "utils/int_array_view.h" + +namespace latinime { +namespace dicttoolkit { +namespace { + +const std::vector<int> getCodePointVector(const char *str) { + std::vector<int> codePoints; + while (*str) { + codePoints.push_back(*str); + ++str; + } + return codePoints; +} + +const WordProperty getDummpWordProperty(const std::vector<int> &&codePoints) { + return WordProperty(std::move(codePoints), UnigramProperty(), std::vector<NgramProperty>()); +} + +TEST(OffdeviceIntermediateDictTest, TestAddWordProperties) { + OffdeviceIntermediateDict dict = OffdeviceIntermediateDict( + OffdeviceIntermediateDictHeader(OffdeviceIntermediateDictHeader::AttributeMap())); + EXPECT_EQ(nullptr, dict.getWordProperty(CodePointArrayView())); + + const WordProperty wordProperty0 = getDummpWordProperty(getCodePointVector("abcd")); + EXPECT_TRUE(dict.addWord(wordProperty0)); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty0.getCodePoints())); + + const WordProperty wordProperty1 = getDummpWordProperty(getCodePointVector("efgh")); + EXPECT_TRUE(dict.addWord(wordProperty1)); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty1.getCodePoints())); + + const WordProperty wordProperty2 = getDummpWordProperty(getCodePointVector("ab")); + EXPECT_TRUE(dict.addWord(wordProperty2)); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty2.getCodePoints())); + + const WordProperty wordProperty3 = getDummpWordProperty(getCodePointVector("abcdefg")); + EXPECT_TRUE(dict.addWord(wordProperty3)); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty3.getCodePoints())); + + const WordProperty wordProperty4 = getDummpWordProperty(getCodePointVector("efef")); + EXPECT_TRUE(dict.addWord(wordProperty4)); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty4.getCodePoints())); + + const WordProperty wordProperty5 = getDummpWordProperty(getCodePointVector("ef")); + EXPECT_TRUE(dict.addWord(wordProperty5)); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty5.getCodePoints())); + + const WordProperty wordProperty6 = getDummpWordProperty(getCodePointVector("abcd")); + EXPECT_FALSE(dict.addWord(wordProperty6)) << "Adding the same word multiple times should fail."; + + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty0.getCodePoints())); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty1.getCodePoints())); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty2.getCodePoints())); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty3.getCodePoints())); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty4.getCodePoints())); + EXPECT_NE(nullptr, dict.getWordProperty(wordProperty5.getCodePoints())); +} + +} // namespace +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/tests/utils/command_utils_test.cpp b/native/dicttoolkit/tests/utils/command_utils_test.cpp new file mode 100644 index 000000000..9d79c9dd9 --- /dev/null +++ b/native/dicttoolkit/tests/utils/command_utils_test.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils/command_utils.h" + +#include <gtest/gtest.h> + +namespace latinime { +namespace dicttoolkit { +namespace { + +TEST(CommandUtilsTests, TestGetCommandType) { + EXPECT_EQ(CommandUtils::getCommandType(""), CommandType::Unknown); + EXPECT_EQ(CommandUtils::getCommandType("abc"), CommandType::Unknown); + EXPECT_EQ(CommandUtils::getCommandType("info"), CommandType::Info); + EXPECT_EQ(CommandUtils::getCommandType("diff"), CommandType::Diff); + EXPECT_EQ(CommandUtils::getCommandType("makedict"), CommandType::Makedict); + EXPECT_EQ(CommandUtils::getCommandType("header"), CommandType::Header); + EXPECT_EQ(CommandUtils::getCommandType("help"), CommandType::Help); +} + +} // namespace +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/dicttoolkit/tests/utils/utf8_utils_test.cpp b/native/dicttoolkit/tests/utils/utf8_utils_test.cpp new file mode 100644 index 000000000..9c59a8b05 --- /dev/null +++ b/native/dicttoolkit/tests/utils/utf8_utils_test.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils/utf8_utils.h" + +#include <gtest/gtest.h> + +#include <vector> + +#include "utils/int_array_view.h" + +namespace latinime { +namespace dicttoolkit { +namespace { + +TEST(Utf8UtilsTests, TestGetCodePoints) { + { + const std::vector<int> codePoints = Utf8Utils::getCodePoints(""); + EXPECT_EQ(0u, codePoints.size()); + } + { + const std::vector<int> codePoints = Utf8Utils::getCodePoints("test"); + EXPECT_EQ(4u, codePoints.size()); + EXPECT_EQ('t', codePoints[0]); + EXPECT_EQ('e', codePoints[1]); + EXPECT_EQ('s', codePoints[2]); + EXPECT_EQ('t', codePoints[3]); + } + { + const std::vector<int> codePoints = Utf8Utils::getCodePoints(u8"\u3042a\u03C2\u0410"); + EXPECT_EQ(4u, codePoints.size()); + EXPECT_EQ(0x3042, codePoints[0]); // HIRAGANA LETTER A + EXPECT_EQ('a', codePoints[1]); + EXPECT_EQ(0x03C2, codePoints[2]); // CYRILLIC CAPITAL LETTER A + EXPECT_EQ(0x0410, codePoints[3]); // GREEK SMALL LETTER FINAL SIGMA + } + { + const std::vector<int> codePoints = Utf8Utils::getCodePoints(u8"\U0001F36A?\U0001F752"); + EXPECT_EQ(3u, codePoints.size()); + EXPECT_EQ(0x1F36A, codePoints[0]); // COOKIE + EXPECT_EQ('?', codePoints[1]); + EXPECT_EQ(0x1F752, codePoints[2]); // ALCHEMICAL SYMBOL FOR STARRED TRIDENT + } + + // Redundant UTF-8 sequences must be rejected. + EXPECT_TRUE(Utf8Utils::getCodePoints("\xC0\xAF").empty()); + EXPECT_TRUE(Utf8Utils::getCodePoints("\xE0\x80\xAF").empty()); + EXPECT_TRUE(Utf8Utils::getCodePoints("\xF0\x80\x80\xAF").empty()); +} + +TEST(Utf8UtilsTests, TestGetUtf8String) { + { + const std::vector<int> codePoints = {'t', 'e', 's', 't'}; + EXPECT_EQ("test", Utf8Utils::getUtf8String(CodePointArrayView(codePoints))); + } + { + const std::vector<int> codePoints = { + 0x00E0 /* LATIN SMALL LETTER A WITH GRAVE */, + 0x03C2 /* GREEK SMALL LETTER FINAL SIGMA */, + 0x0430 /* CYRILLIC SMALL LETTER A */, + 0x3042 /* HIRAGANA LETTER A */, + 0x1F36A /* COOKIE */, + 0x1F752 /* ALCHEMICAL SYMBOL FOR STARRED TRIDENT */ + }; + EXPECT_EQ(u8"\u00E0\u03C2\u0430\u3042\U0001F36A\U0001F752", + Utf8Utils::getUtf8String(CodePointArrayView(codePoints))); + } +} + +} // namespace +} // namespace dicttoolkit +} // namespace latinime diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp index 118f600bb..9c065e0d1 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -35,6 +35,7 @@ #include "utils/int_array_view.h" #include "utils/jni_data_utils.h" #include "utils/log_utils.h" +#include "utils/profiler.h" #include "utils/time_keeper.h" namespace latinime { @@ -43,8 +44,8 @@ class ProximityInfo; static jlong latinime_BinaryDictionary_open(JNIEnv *env, jclass clazz, jstring sourceDir, jlong dictOffset, jlong dictSize, jboolean isUpdatable) { - PROF_OPEN; - PROF_START(66); + PROF_INIT; + PROF_TIMER_START(66); const jsize sourceDirUtf8Length = env->GetStringUTFLength(sourceDir); if (sourceDirUtf8Length <= 0) { AKLOGE("DICT: Can't get sourceDir string"); @@ -63,8 +64,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jclass clazz, jstring s Dictionary *const dictionary = new Dictionary(env, std::move(dictionaryStructureWithBufferPolicy)); - PROF_END(66); - PROF_CLOSE; + PROF_TIMER_END(66); return reinterpret_cast<jlong>(dictionary); } @@ -586,7 +586,7 @@ static bool latinime_BinaryDictionary_migrateNative(JNIEnv *env, jclass clazz, j } if (!dictionaryStructureWithBufferPolicy->addUnigramEntry( CodePointArrayView(wordCodePoints, wordCodePointCount), - wordProperty.getUnigramProperty())) { + &wordProperty.getUnigramProperty())) { LogUtils::logToJava(env, "Cannot add unigram to the new dict."); return false; } @@ -605,7 +605,7 @@ static bool latinime_BinaryDictionary_migrateNative(JNIEnv *env, jclass clazz, j return false; } } - for (const NgramProperty &ngramProperty : *wordProperty.getNgramProperties()) { + for (const NgramProperty &ngramProperty : wordProperty.getNgramProperties()) { if (!dictionaryStructureWithBufferPolicy->addNgramEntry(&ngramProperty)) { LogUtils::logToJava(env, "Cannot add ngram to the new dict."); return false; diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h index 885118524..0e67b4d5a 100644 --- a/native/jni/src/defines.h +++ b/native/jni/src/defines.h @@ -23,10 +23,10 @@ #define AK_FORCE_INLINE inline #endif // __GNUC__ -#if defined(FLAG_DO_PROFILE) || defined(FLAG_DBG) +#if defined(FLAG_DBG) #undef AK_FORCE_INLINE #define AK_FORCE_INLINE inline -#endif // defined(FLAG_DO_PROFILE) || defined(FLAG_DBG) +#endif // defined(FLAG_DBG) // Must be equal to Constants.Dictionary.MAX_WORD_LENGTH in Java #define MAX_WORD_LENGTH 48 @@ -172,69 +172,6 @@ static inline void showStackTrace() { #define INTS_TO_CHARS(input, length, output) #endif // defined(FLAG_DO_PROFILE) || defined(FLAG_DBG) -#ifdef FLAG_DO_PROFILE -// Profiler -#include <time.h> - -#define PROF_BUF_SIZE 100 -static float profile_buf[PROF_BUF_SIZE]; -static float profile_old[PROF_BUF_SIZE]; -static unsigned int profile_counter[PROF_BUF_SIZE]; - -#define PROF_RESET prof_reset() -#define PROF_COUNT(prof_buf_id) ++profile_counter[prof_buf_id] -#define PROF_OPEN do { PROF_RESET; PROF_START(PROF_BUF_SIZE - 1); } while (0) -#define PROF_START(prof_buf_id) do { \ - PROF_COUNT(prof_buf_id); profile_old[prof_buf_id] = (clock()); } while (0) -#define PROF_CLOSE do { PROF_END(PROF_BUF_SIZE - 1); PROF_OUTALL; } while (0) -#define PROF_END(prof_buf_id) profile_buf[prof_buf_id] += ((clock()) - profile_old[prof_buf_id]) -#define PROF_CLOCKOUT(prof_buf_id) \ - AKLOGI("%s : clock is %f", __FUNCTION__, (clock() - profile_old[prof_buf_id])) -#define PROF_OUTALL do { AKLOGI("--- %s ---", __FUNCTION__); prof_out(); } while (0) - -static inline void prof_reset(void) { - for (int i = 0; i < PROF_BUF_SIZE; ++i) { - profile_buf[i] = 0; - profile_old[i] = 0; - profile_counter[i] = 0; - } -} - -static inline void prof_out(void) { - if (profile_counter[PROF_BUF_SIZE - 1] != 1) { - AKLOGI("Error: You must call PROF_OPEN before PROF_CLOSE."); - } - AKLOGI("Total time is %6.3f ms.", - profile_buf[PROF_BUF_SIZE - 1] * 1000.0f / static_cast<float>(CLOCKS_PER_SEC)); - float all = 0.0f; - for (int i = 0; i < PROF_BUF_SIZE - 1; ++i) { - all += profile_buf[i]; - } - if (all < 1.0f) all = 1.0f; - for (int i = 0; i < PROF_BUF_SIZE - 1; ++i) { - if (profile_buf[i] > 0.0f) { - AKLOGI("(%d): Used %4.2f%%, %8.4f ms. Called %d times.", - i, (profile_buf[i] * 100.0f / all), - profile_buf[i] * 1000.0f / static_cast<float>(CLOCKS_PER_SEC), - profile_counter[i]); - } - } -} - -#else // FLAG_DO_PROFILE -#define PROF_BUF_SIZE 0 -#define PROF_RESET -#define PROF_COUNT(prof_buf_id) -#define PROF_OPEN -#define PROF_START(prof_buf_id) -#define PROF_CLOSE -#define PROF_END(prof_buf_id) -#define PROF_CLOCK_OUT(prof_buf_id) -#define PROF_CLOCKOUT(prof_buf_id) -#define PROF_OUTALL - -#endif // FLAG_DO_PROFILE - #ifdef FLAG_DBG #define DEBUG_DICT true #define DEBUG_DICT_FULL false diff --git a/native/jni/src/suggest/core/dictionary/error_type_utils.cpp b/native/jni/src/suggest/core/dictionary/error_type_utils.cpp index 1e2494e92..8f07ce275 100644 --- a/native/jni/src/suggest/core/dictionary/error_type_utils.cpp +++ b/native/jni/src/suggest/core/dictionary/error_type_utils.cpp @@ -31,6 +31,7 @@ const ErrorTypeUtils::ErrorType ErrorTypeUtils::NEW_WORD = 0x100; const ErrorTypeUtils::ErrorType ErrorTypeUtils::ERRORS_TREATED_AS_AN_EXACT_MATCH = NOT_AN_ERROR | MATCH_WITH_WRONG_CASE | MATCH_WITH_MISSING_ACCENT | MATCH_WITH_DIGRAPH; +const ErrorTypeUtils::ErrorType ErrorTypeUtils::ERRORS_TREATED_AS_A_PERFECT_MATCH = NOT_AN_ERROR; const ErrorTypeUtils::ErrorType ErrorTypeUtils::ERRORS_TREATED_AS_AN_EXACT_MATCH_WITH_INTENTIONAL_OMISSION = diff --git a/native/jni/src/suggest/core/dictionary/error_type_utils.h b/native/jni/src/suggest/core/dictionary/error_type_utils.h index fd1d5fcff..e92c509fa 100644 --- a/native/jni/src/suggest/core/dictionary/error_type_utils.h +++ b/native/jni/src/suggest/core/dictionary/error_type_utils.h @@ -52,6 +52,10 @@ class ErrorTypeUtils { return (containedErrorTypes & ~ERRORS_TREATED_AS_AN_EXACT_MATCH) == 0; } + static bool isPerfectMatch(const ErrorType containedErrorTypes) { + return (containedErrorTypes & ~ERRORS_TREATED_AS_A_PERFECT_MATCH) == 0; + } + static bool isExactMatchWithIntentionalOmission(const ErrorType containedErrorTypes) { return (containedErrorTypes & ~ERRORS_TREATED_AS_AN_EXACT_MATCH_WITH_INTENTIONAL_OMISSION) == 0; @@ -73,6 +77,7 @@ class ErrorTypeUtils { DISALLOW_IMPLICIT_CONSTRUCTORS(ErrorTypeUtils); static const ErrorType ERRORS_TREATED_AS_AN_EXACT_MATCH; + static const ErrorType ERRORS_TREATED_AS_A_PERFECT_MATCH; static const ErrorType ERRORS_TREATED_AS_AN_EXACT_MATCH_WITH_INTENTIONAL_OMISSION; }; } // namespace latinime diff --git a/native/jni/src/suggest/core/dictionary/property/historical_info.h b/native/jni/src/suggest/core/dictionary/property/historical_info.h index f9bd6fd8c..e5ce1ea25 100644 --- a/native/jni/src/suggest/core/dictionary/property/historical_info.h +++ b/native/jni/src/suggest/core/dictionary/property/historical_info.h @@ -38,6 +38,7 @@ class HistoricalInfo { return mTimestamp; } + // TODO: Remove int getLevel() const { return mLevel; } diff --git a/native/jni/src/suggest/core/dictionary/property/word_property.h b/native/jni/src/suggest/core/dictionary/property/word_property.h index b5314faaa..d4db3f09f 100644 --- a/native/jni/src/suggest/core/dictionary/property/word_property.h +++ b/native/jni/src/suggest/core/dictionary/property/word_property.h @@ -23,6 +23,7 @@ #include "jni.h" #include "suggest/core/dictionary/property/ngram_property.h" #include "suggest/core/dictionary/property/unigram_property.h" +#include "utils/int_array_view.h" namespace latinime { @@ -33,10 +34,10 @@ class WordProperty { WordProperty() : mCodePoints(), mUnigramProperty(), mNgrams() {} - WordProperty(const std::vector<int> &&codePoints, const UnigramProperty *const unigramProperty, - const std::vector<NgramProperty> *const ngrams) - : mCodePoints(std::move(codePoints)), mUnigramProperty(*unigramProperty), - mNgrams(*ngrams) {} + WordProperty(const std::vector<int> &&codePoints, const UnigramProperty &unigramProperty, + const std::vector<NgramProperty> &ngrams) + : mCodePoints(std::move(codePoints)), mUnigramProperty(unigramProperty), + mNgrams(ngrams) {} void outputProperties(JNIEnv *const env, jintArray outCodePoints, jbooleanArray outFlags, jintArray outProbabilityInfo, jobject outNgramPrevWordsArray, @@ -44,12 +45,16 @@ class WordProperty { jobject outNgramProbabilities, jobject outShortcutTargets, jobject outShortcutProbabilities) const; - const UnigramProperty *getUnigramProperty() const { - return &mUnigramProperty; + const CodePointArrayView getCodePoints() const { + return CodePointArrayView(mCodePoints); } - const std::vector<NgramProperty> *getNgramProperties() const { - return &mNgrams; + const UnigramProperty &getUnigramProperty() const { + return mUnigramProperty; + } + + const std::vector<NgramProperty> &getNgramProperties() const { + return mNgrams; } private: diff --git a/native/jni/src/suggest/core/policy/scoring.h b/native/jni/src/suggest/core/policy/scoring.h index ce3684a1c..b9dda83ad 100644 --- a/native/jni/src/suggest/core/policy/scoring.h +++ b/native/jni/src/suggest/core/policy/scoring.h @@ -30,7 +30,7 @@ class Scoring { public: virtual int calculateFinalScore(const float compoundDistance, const int inputSize, const ErrorTypeUtils::ErrorType containedErrorTypes, const bool forceCommit, - const bool boostExactMatches) const = 0; + const bool boostExactMatches, const bool hasProbabilityZero) const = 0; virtual void getMostProbableString(const DicTraverseSession *const traverseSession, const float weightOfLangModelVsSpatialModel, SuggestionResults *const outSuggestionResults) const = 0; diff --git a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp index 3283f6deb..74db95953 100644 --- a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp +++ b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp @@ -76,6 +76,52 @@ const int SuggestionsOutputUtils::MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT = 16; weightOfLangModelVsSpatialModelToOutputSuggestions, outSuggestionResults); } +/* static */ bool SuggestionsOutputUtils::shouldBlockWord( + const SuggestOptions *const suggestOptions, const DicNode *const terminalDicNode, + const WordAttributes wordAttributes, const bool isLastWord) { + const bool currentWordExactMatch = + ErrorTypeUtils::isExactMatch(terminalDicNode->getContainedErrorTypes()); + // When we have to block offensive words, non-exact matched offensive words should not be + // output. + const bool shouldBlockOffensiveWords = suggestOptions->blockOffensiveWords(); + + const bool isBlockedOffensiveWord = shouldBlockOffensiveWords && + wordAttributes.isPossiblyOffensive(); + + // This function is called in two situations: + // + // 1) At the end of a search, in which case terminalDicNode will point to the last DicNode + // of the search, and isLastWord will be true. + // "fuck" + // | + // \ terminalDicNode (isLastWord=true, currentWordExactMatch=true) + // In this case, if the current word is an exact match, we will always let the word + // through, even if the user is blocking offensive words (it's exactly what they typed!) + // + // 2) In the middle of the search, when we hit a terminal node, to decide whether or not + // to start a new search at root, to try to match the rest of the input. In this case, + // terminalDicNode will point to the terminal node we just hit, and isLastWord will be + // false. + // "fuckvthis" + // | + // \ terminalDicNode (isLastWord=false, currentWordExactMatch=true) + // + // In this case, we should NOT allow the match through (correcting "fuckthis" to "fuck this" + // when offensive words are blocked would be a bad idea). + // + // In the case of a multi-word correction where the offensive word is typed last (eg. + // for the input "allfuck"), this function will be called with isLastWord==true, but + // currentWordExactMatch==false. So we are OK in this case as well. + // "allfuck" + // | + // \ terminalDicNode (isLastWord=true, currentWordExactMatch=false) + if (isLastWord && currentWordExactMatch) { + return false; + } else { + return isBlockedOffensiveWord; + } +} + /* static */ void SuggestionsOutputUtils::outputSuggestionsOfDicNode( const Scoring *const scoringPolicy, DicTraverseSession *traverseSession, const DicNode *const terminalDicNode, const float weightOfLangModelVsSpatialModel, @@ -98,24 +144,16 @@ const int SuggestionsOutputUtils::MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT = 16; const bool isExactMatchWithIntentionalOmission = ErrorTypeUtils::isExactMatchWithIntentionalOmission( terminalDicNode->getContainedErrorTypes()); - const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase(); - // Heuristic: We exclude probability=0 first-char-uppercase words from exact match. - // (e.g. "AMD" and "and") - const bool isSafeExactMatch = isExactMatch - && !(wordAttributes.isPossiblyOffensive() && isFirstCharUppercase); const int outputTypeFlags = (wordAttributes.isPossiblyOffensive() ? Dictionary::KIND_FLAG_POSSIBLY_OFFENSIVE : 0) - | ((isSafeExactMatch && boostExactMatches) ? Dictionary::KIND_FLAG_EXACT_MATCH : 0) + | ((isExactMatch && boostExactMatches) ? Dictionary::KIND_FLAG_EXACT_MATCH : 0) | (isExactMatchWithIntentionalOmission ? Dictionary::KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION : 0); - // Entries that are blacklisted or do not represent a word should not be output. const bool isValidWord = !(wordAttributes.isBlacklisted() || wordAttributes.isNotAWord()); - // When we have to block offensive words, non-exact matched offensive words should not be - // output. - const bool blockOffensiveWords = traverseSession->getSuggestOptions()->blockOffensiveWords(); - const bool isBlockedOffensiveWord = blockOffensiveWords && wordAttributes.isPossiblyOffensive() - && !isSafeExactMatch; + + const bool shouldBlockThisWord = shouldBlockWord(traverseSession->getSuggestOptions(), + terminalDicNode, wordAttributes, true /* isLastWord */); // Increase output score of top typing suggestion to ensure autocorrection. // TODO: Better integration with java side autocorrection logic. @@ -123,11 +161,11 @@ const int SuggestionsOutputUtils::MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT = 16; compoundDistance, traverseSession->getInputSize(), terminalDicNode->getContainedErrorTypes(), (forceCommitMultiWords && terminalDicNode->hasMultipleWords()), - boostExactMatches); + boostExactMatches, wordAttributes.getProbability() == 0); // Don't output invalid or blocked offensive words. However, we still need to submit their // shortcuts if any. - if (isValidWord && !isBlockedOffensiveWord) { + if (isValidWord && !shouldBlockThisWord) { int codePoints[MAX_WORD_LENGTH]; terminalDicNode->outputResult(codePoints); const int indexToPartialCommit = outputSecondWordFirstLetterInputIndex ? diff --git a/native/jni/src/suggest/core/result/suggestions_output_utils.h b/native/jni/src/suggest/core/result/suggestions_output_utils.h index bf8497828..eca1f78b2 100644 --- a/native/jni/src/suggest/core/result/suggestions_output_utils.h +++ b/native/jni/src/suggest/core/result/suggestions_output_utils.h @@ -18,6 +18,7 @@ #define LATINIME_SUGGESTIONS_OUTPUT_UTILS #include "defines.h" +#include "suggest/core/dictionary/word_attributes.h" namespace latinime { @@ -25,11 +26,19 @@ class BinaryDictionaryShortcutIterator; class DicNode; class DicTraverseSession; class Scoring; +class SuggestOptions; class SuggestionResults; class SuggestionsOutputUtils { public: /** + * Returns true if we should block the incoming word, in the context of the user's + * preferences to include or not include possibly offensive words + */ + static bool shouldBlockWord(const SuggestOptions *const suggestOptions, + const DicNode *const terminalDicNode, const WordAttributes wordAttributes, + const bool isLastWord); + /** * Outputs the final list of suggestions (i.e., terminal nodes). */ static void outputSuggestions(const Scoring *const scoringPolicy, diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp index 68a36454e..e5e9b46bf 100644 --- a/native/jni/src/suggest/core/suggest.cpp +++ b/native/jni/src/suggest/core/suggest.cpp @@ -29,6 +29,7 @@ #include "suggest/core/result/suggestions_output_utils.h" #include "suggest/core/session/dic_traverse_session.h" #include "suggest/core/suggest_options.h" +#include "utils/profiler.h" namespace latinime { @@ -48,8 +49,8 @@ void Suggest::getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs, int *times, int *pointerIds, int *inputCodePoints, int inputSize, const float weightOfLangModelVsSpatialModel, SuggestionResults *const outSuggestionResults) const { - PROF_OPEN; - PROF_START(0); + PROF_INIT; + PROF_TIMER_START(0); const float maxSpatialDistance = TRAVERSAL->getMaxSpatialDistance(); DicTraverseSession *tSession = static_cast<DicTraverseSession *>(traverseSession); tSession->setupForGetSuggestions(pInfo, inputCodePoints, inputSize, inputXs, inputYs, times, @@ -57,8 +58,8 @@ void Suggest::getSuggestions(ProximityInfo *pInfo, void *traverseSession, // TODO: Add the way to evaluate cache initializeSearch(tSession); - PROF_END(0); - PROF_START(1); + PROF_TIMER_END(0); + PROF_TIMER_START(1); // keep expanding search dicNodes until all have terminated. while (tSession->getDicTraverseCache()->activeSize() > 0) { @@ -66,12 +67,11 @@ void Suggest::getSuggestions(ProximityInfo *pInfo, void *traverseSession, tSession->getDicTraverseCache()->advanceActiveDicNodes(); tSession->getDicTraverseCache()->advanceInputIndex(inputSize); } - PROF_END(1); - PROF_START(2); + PROF_TIMER_END(1); + PROF_TIMER_START(2); SuggestionsOutputUtils::outputSuggestions( SCORING, tSession, weightOfLangModelVsSpatialModel, outSuggestionResults); - PROF_END(2); - PROF_CLOSE; + PROF_TIMER_END(2); } /** @@ -416,6 +416,11 @@ void Suggest::createNextWordDicNode(DicTraverseSession *traverseSession, DicNode traverseSession->getDictionaryStructurePolicy()->getWordAttributesInContext( dicNode->getPrevWordIds(), dicNode->getWordId(), traverseSession->getMultiBigramMap()); + if (SuggestionsOutputUtils::shouldBlockWord(traverseSession->getSuggestOptions(), + dicNode, wordAttributes, false /* isLastWord */)) { + return; + } + if (!TRAVERSAL->isGoodToTraverseNextWord(dicNode, wordAttributes.getProbability())) { return; } diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h index 44c2f443f..7a5acd7d5 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h +++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h @@ -134,15 +134,17 @@ class HeaderPolicy : public DictionaryHeaderStructurePolicy { // same so we use them for both here. switch (mDictFormatVersion) { case FormatUtils::VERSION_2: - return FormatUtils::VERSION_2; case FormatUtils::VERSION_201: - return FormatUtils::VERSION_201; + AKLOGE("Dictionary versions 2 and 201 are incompatible with this version"); + return FormatUtils::UNKNOWN_VERSION; + case FormatUtils::VERSION_202: + return FormatUtils::VERSION_202; case FormatUtils::VERSION_4_ONLY_FOR_TESTING: return FormatUtils::VERSION_4_ONLY_FOR_TESTING; - case FormatUtils::VERSION_4: - return FormatUtils::VERSION_4; - case FormatUtils::VERSION_4_DEV: - return FormatUtils::VERSION_4_DEV; + case FormatUtils::VERSION_402: + return FormatUtils::VERSION_402; + case FormatUtils::VERSION_403: + return FormatUtils::VERSION_403; default: return FormatUtils::UNKNOWN_VERSION; } @@ -245,7 +247,7 @@ class HeaderPolicy : public DictionaryHeaderStructurePolicy { } bool supportsBeginningOfSentence() const { - return mDictFormatVersion >= FormatUtils::VERSION_4; + return mDictFormatVersion >= FormatUtils::VERSION_402; } const int *getCodePointTable() const { diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp index 41a8b13b8..19ed0d468 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp @@ -111,11 +111,12 @@ typedef DictionaryHeaderStructurePolicy::AttributeMap AttributeMap; switch (version) { case FormatUtils::VERSION_2: case FormatUtils::VERSION_201: - // Version 2 or 201 dictionary writing is not supported. + case FormatUtils::VERSION_202: + // None of the static dictionaries (v2x) support writing return false; case FormatUtils::VERSION_4_ONLY_FOR_TESTING: - case FormatUtils::VERSION_4: - case FormatUtils::VERSION_4_DEV: + case FormatUtils::VERSION_402: + case FormatUtils::VERSION_403: return buffer->writeUintAndAdvancePosition(version /* data */, HEADER_DICTIONARY_VERSION_SIZE, writingPos); default: diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/content/bigram_dict_content.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/content/bigram_dict_content.cpp index 9e1adff70..15ac88319 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/content/bigram_dict_content.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/content/bigram_dict_content.cpp @@ -65,6 +65,8 @@ const BigramEntry BigramDictContent::getBigramEntryAndAdvancePosition( (encodedTargetTerminalId == Ver4DictConstants::INVALID_BIGRAM_TARGET_TERMINAL_ID) ? Ver4DictConstants::NOT_A_TERMINAL_ID : encodedTargetTerminalId; if (mHasHistoricalInfo) { + // Hack for better migration. + count += level; const HistoricalInfo historicalInfo(timestamp, level, count); return BigramEntry(hasNext, probability, &historicalInfo, targetTerminalId); } else { diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/content/probability_dict_content.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/content/probability_dict_content.cpp index ef6166ffd..61ef4aa42 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/content/probability_dict_content.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/content/probability_dict_content.cpp @@ -50,7 +50,8 @@ const ProbabilityEntry ProbabilityDictContent::getProbabilityEntry(const int ter Ver4DictConstants::WORD_LEVEL_FIELD_SIZE, &entryPos); const int count = buffer->readUintAndAdvancePosition( Ver4DictConstants::WORD_COUNT_FIELD_SIZE, &entryPos); - const HistoricalInfo historicalInfo(timestamp, level, count); + // Hack for better migration. + const HistoricalInfo historicalInfo(timestamp, level, count + level); return ProbabilityEntry(flags, probability, &historicalInfo); } else { return ProbabilityEntry(flags, probability); diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp index 08e39ce43..ca7d93b0e 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp @@ -140,7 +140,7 @@ const WordAttributes Ver4PatriciaTriePolicy::getWordAttributesInContext( const WordAttributes Ver4PatriciaTriePolicy::getWordAttributes(const int probability, const PtNodeParams &ptNodeParams) const { - return WordAttributes(probability, ptNodeParams.isBlacklisted(), ptNodeParams.isNotAWord(), + return WordAttributes(probability, false /* isBlacklisted */, ptNodeParams.isNotAWord(), ptNodeParams.getProbability() == 0); } @@ -164,7 +164,7 @@ int Ver4PatriciaTriePolicy::getProbabilityOfWord(const WordIdArrayView prevWordI } const int ptNodePos = getTerminalPtNodePosFromWordId(wordId); const PtNodeParams ptNodeParams(mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos)); - if (ptNodeParams.isDeleted() || ptNodeParams.isBlacklisted() || ptNodeParams.isNotAWord()) { + if (ptNodeParams.isDeleted() || ptNodeParams.isNotAWord()) { return NOT_A_PROBABILITY; } if (prevWordIds.empty()) { @@ -614,7 +614,7 @@ const WordProperty Ver4PatriciaTriePolicy::getWordProperty( const UnigramProperty unigramProperty(ptNodeParams.representsBeginningOfSentence(), ptNodeParams.isNotAWord(), ptNodeParams.isPossiblyOffensive(), ptNodeParams.getProbability(), *historicalInfo, std::move(shortcuts)); - return WordProperty(wordCodePoints.toVector(), &unigramProperty, &ngrams); + return WordProperty(wordCodePoints.toVector(), unigramProperty, ngrams); } int Ver4PatriciaTriePolicy::getNextWordAndNextToken(const int token, int *const outCodePoints, diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp index 372c9e36f..9a9a21b6b 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp @@ -58,7 +58,7 @@ namespace latinime { const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap) { FormatUtils::FORMAT_VERSION dictFormatVersion = FormatUtils::getFormatVersion(formatVersion); switch (dictFormatVersion) { - case FormatUtils::VERSION_4: { + case FormatUtils::VERSION_402: { return newPolicyForOnMemoryV4Dict<backward::v402::Ver4DictConstants, backward::v402::Ver4DictBuffers, backward::v402::Ver4DictBuffers::Ver4DictBuffersPtr, @@ -66,7 +66,7 @@ namespace latinime { dictFormatVersion, locale, attributeMap); } case FormatUtils::VERSION_4_ONLY_FOR_TESTING: - case FormatUtils::VERSION_4_DEV: { + case FormatUtils::VERSION_403: { return newPolicyForOnMemoryV4Dict<Ver4DictConstants, Ver4DictBuffers, Ver4DictBuffers::Ver4DictBuffersPtr, Ver4PatriciaTriePolicy>( dictFormatVersion, locale, attributeMap); @@ -115,9 +115,10 @@ template<class DictConstants, class DictBuffers, class DictBuffersPtr, class Str switch (formatVersion) { case FormatUtils::VERSION_2: case FormatUtils::VERSION_201: - AKLOGE("Given path is a directory but the format is version 2 or 201. path: %s", path); + case FormatUtils::VERSION_202: + AKLOGE("Given path is a directory but the format is version 2xx. path: %s", path); break; - case FormatUtils::VERSION_4: { + case FormatUtils::VERSION_402: { return newPolicyForV4Dict<backward::v402::Ver4DictConstants, backward::v402::Ver4DictBuffers, backward::v402::Ver4DictBuffers::Ver4DictBuffersPtr, @@ -125,7 +126,7 @@ template<class DictConstants, class DictBuffers, class DictBuffersPtr, class Str headerFilePath, formatVersion, std::move(mmappedBuffer)); } case FormatUtils::VERSION_4_ONLY_FOR_TESTING: - case FormatUtils::VERSION_4_DEV: { + case FormatUtils::VERSION_403: { return newPolicyForV4Dict<Ver4DictConstants, Ver4DictBuffers, Ver4DictBuffers::Ver4DictBuffersPtr, Ver4PatriciaTriePolicy>( headerFilePath, formatVersion, std::move(mmappedBuffer)); @@ -177,11 +178,14 @@ template<class DictConstants, class DictBuffers, class DictBuffersPtr, class Str switch (FormatUtils::detectFormatVersion(mmappedBuffer->getReadOnlyByteArrayView())) { case FormatUtils::VERSION_2: case FormatUtils::VERSION_201: + AKLOGE("Dictionary versions 2 and 201 are incompatible with this version"); + break; + case FormatUtils::VERSION_202: return DictionaryStructureWithBufferPolicy::StructurePolicyPtr( new PatriciaTriePolicy(std::move(mmappedBuffer))); case FormatUtils::VERSION_4_ONLY_FOR_TESTING: - case FormatUtils::VERSION_4: - case FormatUtils::VERSION_4_DEV: + case FormatUtils::VERSION_402: + case FormatUtils::VERSION_403: AKLOGE("Given path is a file but the format is version 4. path: %s", path); break; default: diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h index 585e87a24..e52706e07 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h @@ -144,17 +144,6 @@ class PtNodeParams { return PatriciaTrieReadingUtils::isTerminal(mFlags); } - AK_FORCE_INLINE bool isBlacklisted() const { - // Note: this method will be removed in the next change. - // It is used in getProbabilityOfWord and getWordAttributes for both v402 and v403. - // * getProbabilityOfWord will be changed to no longer return NOT_A_PROBABILITY - // when isBlacklisted (i.e. to only check if isNotAWord or isDeleted) - // * getWordAttributes will be changed to always return blacklisted=false and - // isPossiblyOffensive according to the function below (instead of the current - // behaviour of checking if the probability is zero) - return PatriciaTrieReadingUtils::isPossiblyOffensive(mFlags); - } - AK_FORCE_INLINE bool isPossiblyOffensive() const { return PatriciaTrieReadingUtils::isPossiblyOffensive(mFlags); } diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp index 66fd18a52..1a51acad5 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ - #include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h" #include "defines.h" @@ -317,8 +316,8 @@ const WordAttributes PatriciaTriePolicy::getWordAttributesInContext( const WordAttributes PatriciaTriePolicy::getWordAttributes(const int probability, const PtNodeParams &ptNodeParams) const { - return WordAttributes(probability, ptNodeParams.isBlacklisted(), ptNodeParams.isNotAWord(), - ptNodeParams.getProbability() == 0); + return WordAttributes(probability, false /* isBlacklisted */, ptNodeParams.isNotAWord(), + ptNodeParams.isPossiblyOffensive()); } int PatriciaTriePolicy::getProbability(const int unigramProbability, @@ -345,10 +344,9 @@ int PatriciaTriePolicy::getProbabilityOfWord(const WordIdArrayView prevWordIds, const int ptNodePos = getTerminalPtNodePosFromWordId(wordId); const PtNodeParams ptNodeParams = mPtNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos); - if (ptNodeParams.isNotAWord() || ptNodeParams.isBlacklisted()) { - // If this is not a word, or if it's a blacklisted entry, it should behave as - // having no probability outside of the suggestion process (where it should be used - // for shortcuts). + if (ptNodeParams.isNotAWord()) { + // If this is not a word, it should behave as having no probability outside of the + // suggestion process (where it should be used for shortcuts). return NOT_A_PROBABILITY; } if (!prevWordIds.empty()) { @@ -480,7 +478,7 @@ const WordProperty PatriciaTriePolicy::getWordProperty( const UnigramProperty unigramProperty(ptNodeParams.representsBeginningOfSentence(), ptNodeParams.isNotAWord(), ptNodeParams.isPossiblyOffensive(), ptNodeParams.getProbability(), HistoricalInfo(), std::move(shortcuts)); - return WordProperty(wordCodePoints.toVector(), &unigramProperty, &ngrams); + return WordProperty(wordCodePoints.toVector(), unigramProperty, ngrams); } int PatriciaTriePolicy::getNextWordAndNextToken(const int token, int *const outCodePoints, diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h index f4d340f86..9c4ab18e4 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h @@ -105,7 +105,7 @@ class ProbabilityEntry { encodedEntry = (encodedEntry << (Ver4DictConstants::WORD_LEVEL_FIELD_SIZE * CHAR_BIT)) | static_cast<uint8_t>(mHistoricalInfo.getLevel()); encodedEntry = (encodedEntry << (Ver4DictConstants::WORD_COUNT_FIELD_SIZE * CHAR_BIT)) - | static_cast<uint8_t>(mHistoricalInfo.getCount()); + | static_cast<uint16_t>(mHistoricalInfo.getCount()); } else { encodedEntry = (encodedEntry << (Ver4DictConstants::PROBABILITY_SIZE * CHAR_BIT)) | static_cast<uint8_t>(mProbability); diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp index eb6080a24..bd89b8da7 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp @@ -49,8 +49,8 @@ const int Ver4DictConstants::TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3; const int Ver4DictConstants::NOT_A_TERMINAL_ADDRESS = 0; const int Ver4DictConstants::TERMINAL_ID_FIELD_SIZE = 4; const int Ver4DictConstants::TIME_STAMP_FIELD_SIZE = 4; -const int Ver4DictConstants::WORD_LEVEL_FIELD_SIZE = 1; -const int Ver4DictConstants::WORD_COUNT_FIELD_SIZE = 1; +const int Ver4DictConstants::WORD_LEVEL_FIELD_SIZE = 0; +const int Ver4DictConstants::WORD_COUNT_FIELD_SIZE = 2; const uint8_t Ver4DictConstants::FLAG_REPRESENTS_BEGINNING_OF_SENTENCE = 0x1; const uint8_t Ver4DictConstants::FLAG_NOT_A_VALID_ENTRY = 0x2; diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h index 600b5ffe4..13d7a5714 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h @@ -47,6 +47,7 @@ class Ver4DictConstants { static const int NOT_A_TERMINAL_ADDRESS; static const int TERMINAL_ID_FIELD_SIZE; static const int TIME_STAMP_FIELD_SIZE; + // TODO: Remove static const int WORD_LEVEL_FIELD_SIZE; static const int WORD_COUNT_FIELD_SIZE; // Flags in probability entry. diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp index 96d789f58..7449cd02b 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp @@ -146,8 +146,16 @@ void Ver4PatriciaTriePolicy::iterateNgramEntries(const WordIdArrayView prevWordI if (!probabilityEntry.isValid()) { continue; } - const int probability = probabilityEntry.hasHistoricalInfo() ? - 0 : probabilityEntry.getProbability(); + int probability = NOT_A_PROBABILITY; + if (probabilityEntry.hasHistoricalInfo()) { + // TODO: Quit checking count here. + // If count <= 1, the word can be an invaild word. The actual probability should + // be checked using getWordAttributesInContext() in onVisitEntry(). + probability = probabilityEntry.getHistoricalInfo()->getCount() <= 1 ? + NOT_A_PROBABILITY : 0; + } else { + probability = probabilityEntry.getProbability(); + } listener->onVisitEntry(probability, entry.getWordId()); } } @@ -552,7 +560,7 @@ const WordProperty Ver4PatriciaTriePolicy::getWordProperty( wordAttributes.isNotAWord(), wordAttributes.isBlacklisted(), wordAttributes.isPossiblyOffensive(), wordAttributes.getProbability(), *historicalInfo, std::move(shortcuts)); - return WordProperty(wordCodePoints.toVector(), &unigramProperty, &ngrams); + return WordProperty(wordCodePoints.toVector(), unigramProperty, ngrams); } int Ver4PatriciaTriePolicy::getNextWordAndNextToken(const int token, int *const outCodePoints, diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp index 9d8e86675..edcb43678 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp @@ -44,13 +44,13 @@ const int DictFileWritingUtils::SIZE_OF_BUFFER_SIZE_FIELD = 4; TimeKeeper::setCurrentTime(); const FormatUtils::FORMAT_VERSION formatVersion = FormatUtils::getFormatVersion(dictVersion); switch (formatVersion) { - case FormatUtils::VERSION_4: + case FormatUtils::VERSION_402: return createEmptyV4DictFile<backward::v402::Ver4DictConstants, backward::v402::Ver4DictBuffers, backward::v402::Ver4DictBuffers::Ver4DictBuffersPtr>( filePath, localeAsCodePointVector, attributeMap, formatVersion); case FormatUtils::VERSION_4_ONLY_FOR_TESTING: - case FormatUtils::VERSION_4_DEV: + case FormatUtils::VERSION_403: return createEmptyV4DictFile<Ver4DictConstants, Ver4DictBuffers, Ver4DictBuffers::Ver4DictBuffersPtr>( filePath, localeAsCodePointVector, attributeMap, formatVersion); diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp index 0cffe569d..e225c235e 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp @@ -28,15 +28,17 @@ const size_t FormatUtils::DICTIONARY_MINIMUM_SIZE = 12; /* static */ FormatUtils::FORMAT_VERSION FormatUtils::getFormatVersion(const int formatVersion) { switch (formatVersion) { case VERSION_2: - return VERSION_2; case VERSION_201: - return VERSION_201; + AKLOGE("Dictionary versions 2 and 201 are incompatible with this version"); + return UNKNOWN_VERSION; + case VERSION_202: + return VERSION_202; case VERSION_4_ONLY_FOR_TESTING: return VERSION_4_ONLY_FOR_TESTING; - case VERSION_4: - return VERSION_4; - case VERSION_4_DEV: - return VERSION_4_DEV; + case VERSION_402: + return VERSION_402; + case VERSION_403: + return VERSION_403; default: return UNKNOWN_VERSION; } diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h index 96310086b..1616efcce 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h +++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h @@ -31,11 +31,15 @@ class FormatUtils { public: enum FORMAT_VERSION { // These MUST have the same values as the relevant constants in FormatSpec.java. + // TODO: Remove VERSION_2 and VERSION_201 when we: + // * Confirm that old versions of LatinIME download old-format dictionaries + // * We no longer need the corresponding constants on the Java side for dicttool VERSION_2 = 2, VERSION_201 = 201, + VERSION_202 = 202, VERSION_4_ONLY_FOR_TESTING = 399, - VERSION_4 = 402, - VERSION_4_DEV = 403, + VERSION_402 = 402, + VERSION_403 = 403, UNKNOWN_VERSION = -1 }; diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp index a6f9a8b23..856808a74 100644 --- a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp +++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp @@ -24,6 +24,7 @@ const int ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY_FOR_CAPPED = 120; const float ScoringParams::AUTOCORRECT_OUTPUT_THRESHOLD = 1.0f; const float ScoringParams::EXACT_MATCH_PROMOTION = 1.1f; +const float ScoringParams::PERFECT_MATCH_PROMOTION = 1.1f; const float ScoringParams::CASE_ERROR_PENALTY_FOR_EXACT_MATCH = 0.01f; const float ScoringParams::ACCENT_ERROR_PENALTY_FOR_EXACT_MATCH = 0.02f; const float ScoringParams::DIGRAPH_PENALTY_FOR_EXACT_MATCH = 0.03f; diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.h b/native/jni/src/suggest/policyimpl/typing/scoring_params.h index b8f889559..6f327a370 100644 --- a/native/jni/src/suggest/policyimpl/typing/scoring_params.h +++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.h @@ -34,6 +34,7 @@ class ScoringParams { static const int THRESHOLD_SHORT_WORD_LENGTH; static const float EXACT_MATCH_PROMOTION; + static const float PERFECT_MATCH_PROMOTION; static const float CASE_ERROR_PENALTY_FOR_EXACT_MATCH; static const float ACCENT_ERROR_PENALTY_FOR_EXACT_MATCH; static const float DIGRAPH_PENALTY_FOR_EXACT_MATCH; diff --git a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h index 0240bcf54..6acd767ea 100644 --- a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h +++ b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h @@ -44,23 +44,50 @@ class TypingScoring : public Scoring { AK_FORCE_INLINE int calculateFinalScore(const float compoundDistance, const int inputSize, const ErrorTypeUtils::ErrorType containedErrorTypes, const bool forceCommit, - const bool boostExactMatches) const { + const bool boostExactMatches, const bool hasProbabilityZero) const { const float maxDistance = ScoringParams::DISTANCE_WEIGHT_LANGUAGE + static_cast<float>(inputSize) * ScoringParams::TYPING_MAX_OUTPUT_SCORE_PER_INPUT; float score = ScoringParams::TYPING_BASE_OUTPUT_SCORE - compoundDistance / maxDistance; if (forceCommit) { score += ScoringParams::AUTOCORRECT_OUTPUT_THRESHOLD; } - if (boostExactMatches && ErrorTypeUtils::isExactMatch(containedErrorTypes)) { - score += ScoringParams::EXACT_MATCH_PROMOTION; - if ((ErrorTypeUtils::MATCH_WITH_WRONG_CASE & containedErrorTypes) != 0) { - score -= ScoringParams::CASE_ERROR_PENALTY_FOR_EXACT_MATCH; + if (hasProbabilityZero) { + // Previously, when both legitimate 0-frequency words (such as distracters) and + // offensive words were encoded in the same way, distracters would never show up + // when the user blocked offensive words (the default setting, as well as the + // setting for regression tests). + // + // When b/11031090 was fixed and a separate encoding was used for offensive words, + // 0-frequency words would no longer be blocked when they were an "exact match" + // (where case mismatches and accent mismatches would be considered an "exact + // match"). The exact match boosting functionality meant that, for example, when + // the user typed "mt" they would be suggested the word "Mt", although they most + // probably meant to type "my". + // + // For this reason, we introduced this change, which does the following: + // * Defines the "perfect match" as a really exact match, with no room for case or + // accent mismatches + // * When the target word has probability zero (as "Mt" does, because it is a + // distracter), ONLY boost its score if it is a perfect match. + // + // By doing this, when the user types "mt", the word "Mt" will NOT be boosted, and + // they will get "my". However, if the user makes an explicit effort to type "Mt", + // we do boost the word "Mt" so that the user's input is not autocorrected to "My". + if (boostExactMatches && ErrorTypeUtils::isPerfectMatch(containedErrorTypes)) { + score += ScoringParams::PERFECT_MATCH_PROMOTION; } - if ((ErrorTypeUtils::MATCH_WITH_MISSING_ACCENT & containedErrorTypes) != 0) { - score -= ScoringParams::ACCENT_ERROR_PENALTY_FOR_EXACT_MATCH; - } - if ((ErrorTypeUtils::MATCH_WITH_DIGRAPH & containedErrorTypes) != 0) { - score -= ScoringParams::DIGRAPH_PENALTY_FOR_EXACT_MATCH; + } else { + if (boostExactMatches && ErrorTypeUtils::isExactMatch(containedErrorTypes)) { + score += ScoringParams::EXACT_MATCH_PROMOTION; + if ((ErrorTypeUtils::MATCH_WITH_WRONG_CASE & containedErrorTypes) != 0) { + score -= ScoringParams::CASE_ERROR_PENALTY_FOR_EXACT_MATCH; + } + if ((ErrorTypeUtils::MATCH_WITH_MISSING_ACCENT & containedErrorTypes) != 0) { + score -= ScoringParams::ACCENT_ERROR_PENALTY_FOR_EXACT_MATCH; + } + if ((ErrorTypeUtils::MATCH_WITH_DIGRAPH & containedErrorTypes) != 0) { + score -= ScoringParams::DIGRAPH_PENALTY_FOR_EXACT_MATCH; + } } } return static_cast<int>(score * SUGGEST_INTERFACE_OUTPUT_SCALE); diff --git a/native/jni/src/utils/int_array_view.h b/native/jni/src/utils/int_array_view.h index 408373176..e0f671056 100644 --- a/native/jni/src/utils/int_array_view.h +++ b/native/jni/src/utils/int_array_view.h @@ -133,6 +133,29 @@ class IntArrayView { return std::vector<int>(begin(), end()); } + std::vector<IntArrayView> split(const int separator, const int limit = S_INT_MAX) const { + if (limit <= 0) { + return std::vector<IntArrayView>(); + } + std::vector<IntArrayView> result; + if (limit == 1) { + result.emplace_back(mPtr, mSize); + return result; + } + size_t startIndex = 0; + for (size_t i = 0; i < mSize; ++i) { + if (mPtr[i] == separator) { + result.emplace_back(mPtr + startIndex, i - startIndex); + startIndex = i + 1; + if (result.size() >= static_cast<size_t>(limit - 1)) { + break; + } + } + } + result.emplace_back(mPtr + startIndex, mSize - startIndex); + return result; + } + private: DISALLOW_ASSIGNMENT_OPERATOR(IntArrayView); diff --git a/native/jni/src/utils/profiler.h b/native/jni/src/utils/profiler.h new file mode 100644 index 000000000..5f107fed3 --- /dev/null +++ b/native/jni/src/utils/profiler.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_PROFILER_H +#define LATINIME_PROFILER_H + +#ifdef FLAG_DO_PROFILE + +#include "defines.h" + +#include <ctime> +#include <unordered_map> + +namespace latinime { + +class Profiler final { + public: + Profiler(const clockid_t clockId) + : mClockId(clockId), mStartTime(getTimeInMicroSec()), mStartTimes(), mTimes(), + mCounters() {} + + ~Profiler() { + const float totalTime = + static_cast<float>(getTimeInMicroSec() - mStartTime) / 1000.f; + AKLOGI("Total time is %6.3f ms.", totalTime); + for (const auto &time : mTimes) { + AKLOGI("(%d): Used %4.2f%%, %8.4f ms. Called %d times.", time.first, + time.second / totalTime * 100.0f, time.second, mCounters[time.first]); + } + } + + void startTimer(const int id) { + mStartTimes[id] = getTimeInMicroSec(); + } + + void endTimer(const int id) { + mTimes[id] += static_cast<float>(getTimeInMicroSec() - mStartTimes[id]) / 1000.0f; + mCounters[id]++; + } + + operator bool() const { return false; } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Profiler); + + const clockid_t mClockId; + int64_t mStartTime; + std::unordered_map<int, int64_t> mStartTimes; + std::unordered_map<int, float> mTimes; + std::unordered_map<int, int> mCounters; + + int64_t getTimeInMicroSec() { + timespec time; + clock_gettime(mClockId, &time); + return static_cast<int64_t>(time.tv_sec) * 1000000 + + static_cast<int64_t>(time.tv_nsec) / 1000; + } +}; +} // namespace latinime + +#define PROF_INIT Profiler __LATINIME__PROFILER__(CLOCK_THREAD_CPUTIME_ID) +#define PROF_TIMER_START(timer_id) __LATINIME__PROFILER__.startTimer(timer_id) +#define PROF_TIMER_END(timer_id) __LATINIME__PROFILER__.endTimer(timer_id) + +#else // FLAG_DO_PROFILE + +#define PROF_INIT +#define PROF_TIMER_START(timer_id) +#define PROF_TIMER_END(timer_id) + +#endif // FLAG_DO_PROFILE + +#endif /* LATINIME_PROFILER_H */ diff --git a/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content_test.cpp b/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content_test.cpp index 86040f12c..313a9af10 100644 --- a/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content_test.cpp +++ b/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content_test.cpp @@ -52,16 +52,14 @@ TEST(LanguageModelDictContentTest, TestUnigramProbabilityWithHistoricalInfo) { const int flag = 0xF0; const int timestamp = 0x3FFFFFFF; - const int level = 3; const int count = 10; const int wordId = 100; - const HistoricalInfo historicalInfo(timestamp, level, count); + const HistoricalInfo historicalInfo(timestamp, 0 /* level */, count); const ProbabilityEntry probabilityEntry(flag, &historicalInfo); languageModelDictContent.setProbabilityEntry(wordId, &probabilityEntry); const ProbabilityEntry entry = languageModelDictContent.getProbabilityEntry(wordId); EXPECT_EQ(flag, entry.getFlags()); EXPECT_EQ(timestamp, entry.getHistoricalInfo()->getTimestamp()); - EXPECT_EQ(level, entry.getHistoricalInfo()->getLevel()); EXPECT_EQ(count, entry.getHistoricalInfo()->getCount()); // Remove diff --git a/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/probability_entry_test.cpp b/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/probability_entry_test.cpp index 260b347ce..eb78034ba 100644 --- a/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/probability_entry_test.cpp +++ b/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/probability_entry_test.cpp @@ -39,20 +39,18 @@ TEST(ProbabilityEntryTest, TestEncodeDecode) { TEST(ProbabilityEntryTest, TestEncodeDecodeWithHistoricalInfo) { const int flag = 0xF0; const int timestamp = 0x3FFFFFFF; - const int level = 3; - const int count = 10; + const int count = 0xABCD; - const HistoricalInfo historicalInfo(timestamp, level, count); + const HistoricalInfo historicalInfo(timestamp, 0 /* level */, count); const ProbabilityEntry entry(flag, &historicalInfo); const uint64_t encodedEntry = entry.encode(true /* hasHistoricalInfo */); - EXPECT_EQ(0xF03FFFFFFF030Aull, encodedEntry); + EXPECT_EQ(0xF03FFFFFFFABCDull, encodedEntry); const ProbabilityEntry decodedEntry = ProbabilityEntry::decode(encodedEntry, true /* hasHistoricalInfo */); EXPECT_EQ(flag, decodedEntry.getFlags()); EXPECT_EQ(timestamp, decodedEntry.getHistoricalInfo()->getTimestamp()); - EXPECT_EQ(level, decodedEntry.getHistoricalInfo()->getLevel()); EXPECT_EQ(count, decodedEntry.getHistoricalInfo()->getCount()); } diff --git a/native/jni/tests/suggest/policyimpl/dictionary/utils/format_utils_test.cpp b/native/jni/tests/suggest/policyimpl/dictionary/utils/format_utils_test.cpp index 15f560cd1..494200568 100644 --- a/native/jni/tests/suggest/policyimpl/dictionary/utils/format_utils_test.cpp +++ b/native/jni/tests/suggest/policyimpl/dictionary/utils/format_utils_test.cpp @@ -62,14 +62,14 @@ TEST(FormatUtilsTest, TestDetectFormatVersion) { } { const std::vector<uint8_t> buffer = - getBuffer(FormatUtils::MAGIC_NUMBER, FormatUtils::VERSION_4, 0, 0); - EXPECT_EQ(FormatUtils::VERSION_4, FormatUtils::detectFormatVersion( + getBuffer(FormatUtils::MAGIC_NUMBER, FormatUtils::VERSION_402, 0, 0); + EXPECT_EQ(FormatUtils::VERSION_402, FormatUtils::detectFormatVersion( ReadOnlyByteArrayView(buffer.data(), buffer.size()))); } { const std::vector<uint8_t> buffer = - getBuffer(FormatUtils::MAGIC_NUMBER, FormatUtils::VERSION_4_DEV, 0, 0); - EXPECT_EQ(FormatUtils::VERSION_4_DEV, FormatUtils::detectFormatVersion( + getBuffer(FormatUtils::MAGIC_NUMBER, FormatUtils::VERSION_403, 0, 0); + EXPECT_EQ(FormatUtils::VERSION_403, FormatUtils::detectFormatVersion( ReadOnlyByteArrayView(buffer.data(), buffer.size()))); } diff --git a/native/jni/tests/utils/int_array_view_test.cpp b/native/jni/tests/utils/int_array_view_test.cpp index 4757a416b..2fce633f5 100644 --- a/native/jni/tests/utils/int_array_view_test.cpp +++ b/native/jni/tests/utils/int_array_view_test.cpp @@ -151,5 +151,52 @@ TEST(IntArrayViewTest, TestToVector) { EXPECT_EQ(std::vector<int>(), CodePointArrayView().toVector()); } +TEST(IntArrayViewTest, TestSplit) { + EXPECT_TRUE(IntArrayView().split(0, 0).empty()); + { + const auto intArrayViews = IntArrayView().split(0, 1); + EXPECT_EQ(1u, intArrayViews.size()); + EXPECT_TRUE(intArrayViews[0].empty()); + } + { + const auto intArrayViews = IntArrayView().split(0, 100); + EXPECT_EQ(1u, intArrayViews.size()); + EXPECT_TRUE(intArrayViews[0].empty()); + } + + const std::vector<int> intVector = {1, 2, 3, 3, 2, 3}; + const IntArrayView intArrayView(intVector); + { + const auto intArrayViews = intArrayView.split(2); + EXPECT_EQ(3u, intArrayViews.size()); + EXPECT_EQ(std::vector<int>({1}), intArrayViews[0].toVector()); + EXPECT_EQ(std::vector<int>({3, 3}), intArrayViews[1].toVector()); + EXPECT_EQ(std::vector<int>({3}), intArrayViews[2].toVector()); + } + { + const auto intArrayViews = intArrayView.split(2, 2); + EXPECT_EQ(2u, intArrayViews.size()); + EXPECT_EQ(std::vector<int>({1}), intArrayViews[0].toVector()); + EXPECT_EQ(std::vector<int>({3, 3, 2, 3}), intArrayViews[1].toVector()); + } + { + const auto intArrayViews = intArrayView.split(2, 1); + EXPECT_EQ(1u, intArrayViews.size()); + EXPECT_EQ(intVector, intArrayViews[0].toVector()); + } + { + const auto intArrayViews = intArrayView.split(2, 0); + EXPECT_EQ(0u, intArrayViews.size()); + } + { + const auto intArrayViews = intArrayView.split(3); + EXPECT_EQ(4u, intArrayViews.size()); + EXPECT_EQ(std::vector<int>({1, 2}), intArrayViews[0].toVector()); + EXPECT_EQ(std::vector<int>(), intArrayViews[1].toVector()); + EXPECT_EQ(std::vector<int>({2}), intArrayViews[2].toVector()); + EXPECT_EQ(std::vector<int>(), intArrayViews[3].toVector()); + } +} + } // namespace } // namespace latinime diff --git a/tests/Android.mk b/tests/Android.mk index 7cdabf249..0b5449143 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -LOCAL_PATH:= $(call my-dir) +LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # We only want this apk build for tests. diff --git a/tests/src/com/android/inputmethod/keyboard/action/ActionTestsBase.java b/tests/src/com/android/inputmethod/keyboard/action/ActionTestsBase.java index 94caf51ed..1ea68e471 100644 --- a/tests/src/com/android/inputmethod/keyboard/action/ActionTestsBase.java +++ b/tests/src/com/android/inputmethod/keyboard/action/ActionTestsBase.java @@ -30,7 +30,7 @@ import com.android.inputmethod.keyboard.KeyboardLayoutSetTestsBase; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyVisual; import com.android.inputmethod.latin.common.Constants; -import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.utils.RunInLocale; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; diff --git a/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java b/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java index e6198015a..6bb255b01 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java @@ -30,10 +30,11 @@ import com.android.inputmethod.latin.RichInputMethodSubtype; import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; -import java.util.Arrays; -import java.util.List; +import java.util.ArrayList; import java.util.Locale; +import javax.annotation.Nonnull; + @SmallTest public class LanguageOnSpacebarHelperTests extends AndroidTestCase { private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper = @@ -48,6 +49,7 @@ public class LanguageOnSpacebarHelperTests extends AndroidTestCase { RichInputMethodSubtype FR_CH_SWISS; RichInputMethodSubtype FR_CH_QWERTY; RichInputMethodSubtype FR_CH_QWERTZ; + RichInputMethodSubtype IW_HEBREW; RichInputMethodSubtype ZZ_QWERTY; @Override @@ -56,116 +58,160 @@ public class LanguageOnSpacebarHelperTests extends AndroidTestCase { final Context context = getContext(); RichInputMethodManager.init(context); mRichImm = RichInputMethodManager.getInstance(); - SubtypeLocaleUtils.init(context); - - EN_US_QWERTY = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - Locale.US.toString(), "qwerty")); - EN_GB_QWERTY = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - Locale.UK.toString(), "qwerty")); - FR_AZERTY = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - Locale.FRENCH.toString(), "azerty")); - FR_CA_QWERTY = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - Locale.CANADA_FRENCH.toString(), "qwerty")); - FR_CH_SWISS = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - "fr_CH", "swiss")); + + EN_US_QWERTY = findSubtypeOf(Locale.US.toString(), "qwerty"); + EN_GB_QWERTY = findSubtypeOf(Locale.UK.toString(), "qwerty"); + FR_AZERTY = findSubtypeOf(Locale.FRENCH.toString(), "azerty"); + FR_CA_QWERTY = findSubtypeOf(Locale.CANADA_FRENCH.toString(), "qwerty"); + FR_CH_SWISS = findSubtypeOf("fr_CH", "swiss"); FR_CH_QWERTZ = new RichInputMethodSubtype( AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype("fr_CH", "qwertz")); FR_CH_QWERTY = new RichInputMethodSubtype( AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype("fr_CH", "qwerty")); - ZZ_QWERTY = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - SubtypeLocaleUtils.NO_LANGUAGE, "qwerty")); + IW_HEBREW = findSubtypeOf("iw", "hebrew"); + ZZ_QWERTY = findSubtypeOf(SubtypeLocaleUtils.NO_LANGUAGE, "qwerty"); + } + + @Nonnull + private RichInputMethodSubtype findSubtypeOf(final String localeString, + final String keyboardLayoutSetName) { + final InputMethodSubtype subtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + localeString, keyboardLayoutSetName); + if (subtype == null) { + throw new RuntimeException("Can't find subtype of " + localeString + " with " + + keyboardLayoutSetName); + } + return new RichInputMethodSubtype(subtype); + } + + private void enableSubtypes(final RichInputMethodSubtype ... subtypes) { + final ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>(); + for (final RichInputMethodSubtype subtype : subtypes) { + enabledSubtypes.add(subtype.getRawSubtype()); + } + mLanguageOnSpacebarHelper.onUpdateEnabledSubtypes(enabledSubtypes); + } + + private void assertFormatType(final RichInputMethodSubtype subtype, + final boolean implicitlyEnabledSubtype, final Locale systemLocale, + final int expectedFormat) { + mLanguageOnSpacebarHelper.onSubtypeChanged(subtype, implicitlyEnabledSubtype, systemLocale); + assertEquals(subtype.getLocales()[0] + " implicitly=" + implicitlyEnabledSubtype + + " in " + systemLocale, expectedFormat, + mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype)); + } + + public void testOneSubtypeImplicitlyEnabled() { + enableSubtypes(EN_US_QWERTY); + assertFormatType(EN_US_QWERTY, true, Locale.US, FORMAT_TYPE_NONE); + + enableSubtypes(EN_GB_QWERTY); + assertFormatType(EN_GB_QWERTY, true, Locale.UK, FORMAT_TYPE_NONE); + + enableSubtypes(FR_AZERTY); + assertFormatType(FR_AZERTY, true, Locale.FRANCE, FORMAT_TYPE_NONE); + + enableSubtypes(FR_CA_QWERTY); + assertFormatType(FR_CA_QWERTY, true, Locale.CANADA_FRENCH, FORMAT_TYPE_NONE); + } + + public void testOneSubtypeExplicitlyEnabled() { + enableSubtypes(EN_US_QWERTY); + assertFormatType(EN_US_QWERTY, false, Locale.UK, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(EN_US_QWERTY, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + + enableSubtypes(EN_GB_QWERTY); + assertFormatType(EN_GB_QWERTY, false, Locale.US, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(EN_GB_QWERTY, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + + enableSubtypes(FR_AZERTY); + assertFormatType(FR_AZERTY, false, Locale.US, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_AZERTY, false, Locale.CANADA_FRENCH, FORMAT_TYPE_LANGUAGE_ONLY); + + enableSubtypes(FR_CA_QWERTY); + assertFormatType(FR_CA_QWERTY, false, Locale.US, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CA_QWERTY, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); } - private static List<InputMethodSubtype> asList(final InputMethodSubtype ... subtypes) { - return Arrays.asList(subtypes); + public void testOneSubtypeImplicitlyEnabledWithNoLanguageSubtype() { + final Locale Locale_IW = new Locale("iw"); + enableSubtypes(IW_HEBREW, ZZ_QWERTY); + // TODO: Should this be FORMAT_TYPE_NONE? + assertFormatType(IW_HEBREW, true, Locale_IW, FORMAT_TYPE_LANGUAGE_ONLY); + // TODO: Should this be FORMAT_TYPE_NONE? + assertFormatType(ZZ_QWERTY, true, Locale_IW, FORMAT_TYPE_FULL_LOCALE); } - public void testOneSubtype() { - mLanguageOnSpacebarHelper.updateEnabledSubtypes(asList(EN_US_QWERTY.getRawSubtype())); - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */); - assertEquals("one same English (US)", FORMAT_TYPE_NONE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); - assertEquals("one same NoLanguage", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); - - mLanguageOnSpacebarHelper.updateEnabledSubtypes(asList(FR_AZERTY.getRawSubtype())); - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */); - assertEquals("one diff English (US)", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); - assertEquals("one diff NoLanguage", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); + public void testTwoSubtypesExplicitlyEnabled() { + enableSubtypes(EN_US_QWERTY, FR_AZERTY); + assertFormatType(EN_US_QWERTY, false, Locale.US, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_AZERTY, false, Locale.US, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(EN_US_QWERTY, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_AZERTY, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(EN_US_QWERTY, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_AZERTY, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); + + enableSubtypes(EN_US_QWERTY, ZZ_QWERTY); + assertFormatType(EN_US_QWERTY, false, Locale.US, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(ZZ_QWERTY, false, Locale.US, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(EN_US_QWERTY, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(ZZ_QWERTY, false, Locale.FRANCE, FORMAT_TYPE_FULL_LOCALE); + } - public void testTwoSubtypes() { - mLanguageOnSpacebarHelper.updateEnabledSubtypes(asList(EN_US_QWERTY.getRawSubtype(), - FR_AZERTY.getRawSubtype())); - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */); - assertEquals("two same English (US)", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); - assertEquals("two same French)", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY)); - assertEquals("two same NoLanguage", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); - - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */); - assertEquals("two diff English (US)", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); - assertEquals("two diff French", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY)); - assertEquals("two diff NoLanguage", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); + public void testMultiSubtypeWithSameLanuageAndSameLayout() { + // Explicitly enable en_US, en_GB, fr_FR, and no language keyboards. + enableSubtypes(EN_US_QWERTY, EN_GB_QWERTY, FR_CA_QWERTY, ZZ_QWERTY); + + assertFormatType(EN_US_QWERTY, false, Locale.US, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(EN_GB_QWERTY, false, Locale.US, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(FR_CA_QWERTY, false, Locale.US, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(ZZ_QWERTY, false, Locale.US, FORMAT_TYPE_FULL_LOCALE); + + assertFormatType(EN_US_QWERTY, false, Locale.JAPAN, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(EN_GB_QWERTY, false, Locale.JAPAN, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(FR_CA_QWERTY, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(ZZ_QWERTY, false, Locale.JAPAN, FORMAT_TYPE_FULL_LOCALE); } - public void testSameLanuageSubtypes() { - mLanguageOnSpacebarHelper.updateEnabledSubtypes( - asList(EN_US_QWERTY.getRawSubtype(), EN_GB_QWERTY.getRawSubtype(), - FR_AZERTY.getRawSubtype(), ZZ_QWERTY.getRawSubtype())); - - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */); - assertEquals("two same English (US)", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); - assertEquals("two same English (UK)", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_GB_QWERTY)); - assertEquals("two same NoLanguage", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); - - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */); - assertEquals("two diff English (US)", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY)); - assertEquals("two diff English (UK)", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_GB_QWERTY)); - assertEquals("two diff NoLanguage", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY)); + public void testMultiSubtypesWithSameLanguageButHaveDifferentLayout() { + enableSubtypes(FR_AZERTY, FR_CA_QWERTY, FR_CH_SWISS, FR_CH_QWERTZ); + + assertFormatType(FR_AZERTY, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CA_QWERTY, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CH_SWISS, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CH_QWERTZ, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + + assertFormatType(FR_AZERTY, false, Locale.CANADA_FRENCH, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CA_QWERTY, false, Locale.CANADA_FRENCH, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CH_SWISS, false, Locale.CANADA_FRENCH, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CH_QWERTZ, false, Locale.CANADA_FRENCH, FORMAT_TYPE_LANGUAGE_ONLY); + + assertFormatType(FR_AZERTY, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CA_QWERTY, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CH_SWISS, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CH_QWERTZ, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); } - public void testMultiSameLanuageSubtypes() { - mLanguageOnSpacebarHelper.updateEnabledSubtypes( - asList(FR_AZERTY.getRawSubtype(), FR_CA_QWERTY.getRawSubtype(), - FR_CH_SWISS.getRawSubtype(), FR_CH_QWERTY.getRawSubtype(), - FR_CH_QWERTZ.getRawSubtype())); - - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */); - assertEquals("multi same French", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY)); - assertEquals("multi same French (CA)", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CA_QWERTY)); - assertEquals("multi same French (CH)", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_SWISS)); - assertEquals("multi same French (CH) (QWERTY)", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTY)); - assertEquals("multi same French (CH) (QWERTZ)", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTZ)); - - mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */); - assertEquals("multi diff French", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY)); - assertEquals("multi diff French (CA)", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CA_QWERTY)); - assertEquals("multi diff French (CH)", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_SWISS)); - assertEquals("multi diff French (CH) (QWERTY)", FORMAT_TYPE_FULL_LOCALE, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTY)); - assertEquals("multi diff French (CH) (QWERTZ)", FORMAT_TYPE_LANGUAGE_ONLY, - mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTZ)); + public void testMultiSubtypesWithSameLanguageAndMayHaveSameLayout() { + enableSubtypes(FR_AZERTY, FR_CA_QWERTY, FR_CH_SWISS, FR_CH_QWERTY, FR_CH_QWERTZ); + + assertFormatType(FR_AZERTY, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CA_QWERTY, false, Locale.FRANCE, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(FR_CH_SWISS, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CH_QWERTY, false, Locale.FRANCE, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(FR_CH_QWERTZ, false, Locale.FRANCE, FORMAT_TYPE_LANGUAGE_ONLY); + + assertFormatType(FR_AZERTY, false, Locale.CANADA_FRENCH, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CA_QWERTY, false, Locale.CANADA_FRENCH, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(FR_CH_SWISS, false, Locale.CANADA_FRENCH, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CH_QWERTY, false, Locale.CANADA_FRENCH, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(FR_CH_QWERTZ, false, Locale.CANADA_FRENCH, FORMAT_TYPE_LANGUAGE_ONLY); + + assertFormatType(FR_AZERTY, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CA_QWERTY, false, Locale.JAPAN, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(FR_CH_SWISS, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); + assertFormatType(FR_CH_QWERTY, false, Locale.JAPAN, FORMAT_TYPE_FULL_LOCALE); + assertFormatType(FR_CH_QWERTZ, false, Locale.JAPAN, FORMAT_TYPE_LANGUAGE_ONLY); } } diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBengaliBD.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBengaliBD.java index 62625890e..2d38c874d 100644 --- a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBengaliBD.java +++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsBengaliBD.java @@ -46,6 +46,11 @@ public final class TestsBengaliBD extends LayoutTestsBase { } @Override + public ExpectedKey[] getSpaceKeys(final boolean isPhone) { + return joinKeys(LANGUAGE_SWITCH_KEY, SPACE_KEY, key(ZWNJ_KEY, ZWJ_KEY)); + } + + @Override public ExpectedKey getCurrencyKey() { return CURRENCY_RUPEE; } // U+09F3: "৳" BENGALI RUPEE SIGN diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java index b9764488e..cff489dd5 100644 --- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java +++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java @@ -22,6 +22,8 @@ import android.util.Pair; import com.android.inputmethod.latin.NgramContext.WordInfo; import com.android.inputmethod.latin.common.CodePointUtils; +import com.android.inputmethod.latin.common.FileUtils; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; import com.android.inputmethod.latin.makedict.DictDecoder; import com.android.inputmethod.latin.makedict.DictionaryHeader; @@ -30,8 +32,6 @@ import com.android.inputmethod.latin.makedict.FusionDictionary; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.FileUtils; -import com.android.inputmethod.latin.utils.LocaleUtils; import com.android.inputmethod.latin.utils.WordInputEventForPersonalization; import java.io.File; @@ -49,7 +49,7 @@ public class BinaryDictionaryDecayingTests extends AndroidTestCase { private static final String TEST_LOCALE = "test"; private static final int DUMMY_PROBABILITY = 0; private static final int[] DICT_FORMAT_VERSIONS = - new int[] { FormatSpec.VERSION4, FormatSpec.VERSION4_DEV }; + new int[] { FormatSpec.VERSION402, FormatSpec.VERSION403, FormatSpec.VERSION4_DEV }; private static final String DICTIONARY_ID = "TestDecayingBinaryDictionary"; private int mCurrentTime = 0; @@ -73,11 +73,11 @@ public class BinaryDictionaryDecayingTests extends AndroidTestCase { } private static boolean supportsCountBasedNgram(final int formatVersion) { - return formatVersion >= FormatSpec.VERSION4_DEV; + return formatVersion >= FormatSpec.VERSION403; } private static boolean supportsNgram(final int formatVersion) { - return formatVersion >= FormatSpec.VERSION4_DEV; + return formatVersion >= FormatSpec.VERSION403; } private void onInputWord(final BinaryDictionary binaryDictionary, final String word, diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java index a1ae93c2f..60d2de18c 100644 --- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java +++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java @@ -24,12 +24,12 @@ import android.util.Pair; import com.android.inputmethod.latin.NgramContext.WordInfo; import com.android.inputmethod.latin.common.CodePointUtils; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.FileUtils; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.makedict.FormatSpec; import com.android.inputmethod.latin.makedict.WeightedString; import com.android.inputmethod.latin.makedict.WordProperty; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.FileUtils; import java.io.File; import java.io.IOException; @@ -45,19 +45,11 @@ public class BinaryDictionaryTests extends AndroidTestCase { private static final String TEST_DICT_FILE_EXTENSION = ".testDict"; private static final String TEST_LOCALE = "test"; private static final int[] DICT_FORMAT_VERSIONS = - new int[] { FormatSpec.VERSION4, FormatSpec.VERSION4_DEV }; + new int[] { FormatSpec.VERSION402, FormatSpec.VERSION403, FormatSpec.VERSION4_DEV }; private static final String DICTIONARY_ID = "TestBinaryDictionary"; - private static boolean canCheckBigramProbability(final int formatVersion) { - return formatVersion > FormatSpec.VERSION401; - } - - private static boolean supportsBeginningOfSentence(final int formatVersion) { - return formatVersion > FormatSpec.VERSION401; - } - private static boolean supportsNgram(final int formatVersion) { - return formatVersion >= FormatSpec.VERSION4_DEV; + return formatVersion >= FormatSpec.VERSION403; } private HashSet<File> mDictFilesToBeDeleted = new HashSet<>(); @@ -84,19 +76,13 @@ public class BinaryDictionaryTests extends AndroidTestCase { private File createEmptyDictionaryWithAttributesAndGetFile(final int formatVersion, final HashMap<String, String> attributeMap) { - if (formatVersion == FormatSpec.VERSION4 - || formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING - || formatVersion == FormatSpec.VERSION4_DEV) { - try { - final File dictFile = createEmptyVer4DictionaryAndGetFile(formatVersion, - attributeMap); - mDictFilesToBeDeleted.add(dictFile); - return dictFile; - } catch (final IOException e) { - fail(e.toString()); - } - } else { - fail("Dictionary format version " + formatVersion + " is not supported."); + try { + final File dictFile = createEmptyVer4DictionaryAndGetFile(formatVersion, + attributeMap); + mDictFilesToBeDeleted.add(dictFile); + return dictFile; + } catch (final IOException e) { + fail(e.toString()); } return null; } @@ -350,18 +336,14 @@ public class BinaryDictionaryTests extends AndroidTestCase { assertTrue(isValidBigram(binaryDictionary, "aaa", "bcc")); assertTrue(isValidBigram(binaryDictionary, "abb", "aaa")); assertTrue(isValidBigram(binaryDictionary, "abb", "bcc")); - if (canCheckBigramProbability(formatVersion)) { - assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb")); - assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc")); - assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa")); - assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc")); - } + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc")); addBigramWords(binaryDictionary, "aaa", "abb", updatedBigramProbability); - if (canCheckBigramProbability(formatVersion)) { - assertEquals(updatedBigramProbability, - getBigramProbability(binaryDictionary, "aaa", "abb")); - } + assertEquals(updatedBigramProbability, + getBigramProbability(binaryDictionary, "aaa", "abb")); assertFalse(isValidBigram(binaryDictionary, "bcc", "aaa")); assertFalse(isValidBigram(binaryDictionary, "bcc", "bbc")); @@ -381,17 +363,12 @@ public class BinaryDictionaryTests extends AndroidTestCase { addUnigramWord(binaryDictionary, "abc", unigramProbability); addUnigramWord(binaryDictionary, "f", unigramProbability); - if (canCheckBigramProbability(formatVersion)) { - assertEquals(bigramProbability, - getBigramProbability(binaryDictionary, "abcde", "fghij")); - } + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abcde", "fghij")); assertEquals(Dictionary.NOT_A_PROBABILITY, getBigramProbability(binaryDictionary, "abcde", "fgh")); addBigramWords(binaryDictionary, "abcde", "fghij", updatedBigramProbability); - if (canCheckBigramProbability(formatVersion)) { - assertEquals(updatedBigramProbability, - getBigramProbability(binaryDictionary, "abcde", "fghij")); - } + assertEquals(updatedBigramProbability, + getBigramProbability(binaryDictionary, "abcde", "fghij")); } public void testRandomlyAddBigramWords() { @@ -441,10 +418,8 @@ public class BinaryDictionaryTests extends AndroidTestCase { final int bigramProbability = bigramProbabilities.get(bigram); assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY, isValidBigram(binaryDictionary, bigram.first, bigram.second)); - if (canCheckBigramProbability(formatVersion)) { - assertEquals(bigramProbability, - getBigramProbability(binaryDictionary, bigram.first, bigram.second)); - } + assertEquals(bigramProbability, + getBigramProbability(binaryDictionary, bigram.first, bigram.second)); } } @@ -594,12 +569,10 @@ public class BinaryDictionaryTests extends AndroidTestCase { assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa")); assertEquals(unigramProbability, binaryDictionary.getFrequency("abb")); assertEquals(unigramProbability, binaryDictionary.getFrequency("bcc")); - if (canCheckBigramProbability(formatVersion)) { - assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb")); - assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc")); - assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa")); - assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc")); - } + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa")); + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc")); assertFalse(isValidBigram(binaryDictionary, "bcc", "aaa")); assertFalse(isValidBigram(binaryDictionary, "bcc", "bbc")); assertFalse(isValidBigram(binaryDictionary, "aaa", "aaa")); @@ -661,10 +634,8 @@ public class BinaryDictionaryTests extends AndroidTestCase { final int bigramProbability = bigramProbabilities.get(bigram); assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY, isValidBigram(binaryDictionary, bigram.first, bigram.second)); - if (canCheckBigramProbability(formatVersion)) { - assertEquals(bigramProbability, - getBigramProbability(binaryDictionary, bigram.first, bigram.second)); - } + assertEquals(bigramProbability, + getBigramProbability(binaryDictionary, bigram.first, bigram.second)); } } @@ -768,10 +739,8 @@ public class BinaryDictionaryTests extends AndroidTestCase { probability = Dictionary.NOT_A_PROBABILITY; } - if (canCheckBigramProbability(formatVersion)) { - assertEquals(probability, - getBigramProbability(binaryDictionary, bigram.first, bigram.second)); - } + assertEquals(probability, + getBigramProbability(binaryDictionary, bigram.first, bigram.second)); assertEquals(probability != Dictionary.NOT_A_PROBABILITY, isValidBigram(binaryDictionary, bigram.first, bigram.second)); } @@ -971,10 +940,8 @@ public class BinaryDictionaryTests extends AndroidTestCase { for (final WeightedString bigramTarget : wordProperty.getBigrams()) { final String word1 = bigramTarget.mWord; assertTrue(bigramWord1s.contains(word1)); - if (canCheckBigramProbability(formatVersion)) { - final int bigramProbability = bigramProbabilities.get(new Pair<>(word0, word1)); - assertEquals(bigramProbability, bigramTarget.getProbability()); - } + final int bigramProbability = bigramProbabilities.get(new Pair<>(word0, word1)); + assertEquals(bigramProbability, bigramTarget.getProbability()); } } } @@ -1057,10 +1024,8 @@ public class BinaryDictionaryTests extends AndroidTestCase { final String word1 = bigramTarget.mWord; assertTrue(bigramWord1s.contains(word1)); final Pair<String, String> bigram = new Pair<>(word0, word1); - if (canCheckBigramProbability(formatVersion)) { - final int bigramProbability = bigramProbabilitiesToCheckLater.get(bigram); - assertEquals(bigramProbability, bigramTarget.getProbability()); - } + final int bigramProbability = bigramProbabilitiesToCheckLater.get(bigram); + assertEquals(bigramProbability, bigramTarget.getProbability()); bigramSet.remove(bigram); } } @@ -1198,7 +1163,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { public void testPossiblyOffensiveAttributeMaintained() { final BinaryDictionary binaryDictionary = - getEmptyBinaryDictionary(FormatSpec.VERSION4_DEV); + getEmptyBinaryDictionary(FormatSpec.VERSION403); binaryDictionary.addUnigramEntry("ddd", 100, null, Dictionary.NOT_A_PROBABILITY, false, true, true, 0); WordProperty wordProperty = binaryDictionary.getWordProperty("ddd", false); @@ -1236,11 +1201,9 @@ public class BinaryDictionaryTests extends AndroidTestCase { assertEquals(toFormatVersion, binaryDictionary.getFormatVersion()); assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa")); assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb")); - if (canCheckBigramProbability(toFormatVersion)) { - assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bbb")); - assertEquals(bigramProbability, binaryDictionary.getNgramProbability( - NgramContext.BEGINNING_OF_SENTENCE, "aaa")); - } + assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bbb")); + assertEquals(bigramProbability, binaryDictionary.getNgramProbability( + NgramContext.BEGINNING_OF_SENTENCE, "aaa")); assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb")); WordProperty wordProperty = binaryDictionary.getWordProperty("ccc", false /* isBeginningOfSentence */); @@ -1311,10 +1274,8 @@ public class BinaryDictionaryTests extends AndroidTestCase { binaryDictionary.getPropertyForGettingStats(BinaryDictionary.UNIGRAM_COUNT_QUERY))); for (final Pair<String, String> bigram : bigrams) { - if (canCheckBigramProbability(toFormatVersion)) { - assertEquals((int)bigramProbabilities.get(bigram), - getBigramProbability(binaryDictionary, bigram.first, bigram.second)); - } + assertEquals((int)bigramProbabilities.get(bigram), + getBigramProbability(binaryDictionary, bigram.first, bigram.second)); assertTrue(isValidBigram(binaryDictionary, bigram.first, bigram.second)); } assertEquals(bigramProbabilities.size(), Integer.parseInt( @@ -1323,9 +1284,7 @@ public class BinaryDictionaryTests extends AndroidTestCase { public void testBeginningOfSentence() { for (final int formatVersion : DICT_FORMAT_VERSIONS) { - if (supportsBeginningOfSentence(formatVersion)) { - testBeginningOfSentence(formatVersion); - } + testBeginningOfSentence(formatVersion); } } diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java index 926a2d3e1..00e0b52cb 100644 --- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java +++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java @@ -44,10 +44,10 @@ import com.android.inputmethod.latin.Dictionary.PhonyDictionary; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.common.Constants; import com.android.inputmethod.latin.common.InputPointers; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.settings.DebugSettings; import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.LocaleUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import java.util.Locale; @@ -387,7 +387,7 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */, 0 /* id */); - SubtypeSwitcher.forceSubtype(subtype); + RichInputMethodManager.forceSubtype(subtype); mLatinIME.onCurrentInputMethodSubtypeChanged(subtype); runMessages(); mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard(); diff --git a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java b/tests/src/com/android/inputmethod/latin/RichInputMethodSubtypeTests.java index 83afd782d..aed7d6ad6 100644 --- a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/RichInputMethodSubtypeTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.inputmethod.latin.utils; +package com.android.inputmethod.latin; import android.content.Context; import android.content.res.Resources; @@ -26,12 +26,15 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.RichInputMethodSubtype; +import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; +import com.android.inputmethod.latin.utils.RunInLocale; +import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import java.util.ArrayList; import java.util.Locale; @SmallTest -public class SpacebarLanguageUtilsTests extends AndroidTestCase { +public class RichInputMethodSubtypeTests extends AndroidTestCase { // All input method subtypes of LatinIME. private final ArrayList<RichInputMethodSubtype> mSubtypesList = new ArrayList<>(); diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java index 20256e670..8ae475f7e 100644 --- a/tests/src/com/android/inputmethod/latin/WordComposerTests.java +++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java @@ -20,8 +20,8 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.CoordinateUtils; import com.android.inputmethod.latin.common.StringUtils; -import com.android.inputmethod.latin.utils.CoordinateUtils; /** * Unit tests for WordComposer. diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java index a35fa13ce..d833b9736 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java @@ -67,6 +67,8 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { private static final SparseArray<List<Integer>> sChainBigrams = new SparseArray<>(); private static final HashMap<String, List<String>> sShortcuts = new HashMap<>(); + final Random mRandom; + public BinaryDictDecoderEncoderTests() { this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS); } @@ -75,10 +77,10 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { super(); BinaryDictionaryUtils.setCurrentTimeForTest(0); Log.e(TAG, "Testing dictionary: seed is " + seed); - final Random random = new Random(seed); + mRandom = new Random(seed); sWords.clear(); sWordsWithVariousCodePoints.clear(); - generateWords(maxUnigrams, random); + generateWords(maxUnigrams, mRandom); for (int i = 0; i < sWords.size(); ++i) { sChainBigrams.put(i, new ArrayList<Integer>()); @@ -96,10 +98,10 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { sShortcuts.clear(); for (int i = 0; i < NUM_OF_NODES_HAVING_SHORTCUTS; ++i) { - final int from = Math.abs(random.nextInt()) % sWords.size(); + final int from = Math.abs(mRandom.nextInt()) % sWords.size(); sShortcuts.put(sWords.get(from), new ArrayList<String>()); for (int j = 0; j < NUM_OF_SHORTCUTS; ++j) { - final int to = Math.abs(random.nextInt()) % sWords.size(); + final int to = Math.abs(mRandom.nextInt()) % sWords.size(); sShortcuts.get(sWords.get(from)).add(sWords.get(to)); } } @@ -314,14 +316,14 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final String dictVersion = Long.toString(System.currentTimeMillis()); final String codePointTableAttribute = DictionaryHeader.CODE_POINT_TABLE_KEY; final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, - BinaryDictUtils.VERSION201_OPTIONS, getContext().getCacheDir()); + BinaryDictUtils.STATIC_OPTIONS, getContext().getCacheDir()); // Write a test dictionary final DictEncoder dictEncoder = new Ver2DictEncoder(file, Ver2DictEncoder.CODE_POINT_TABLE_ON); final FormatSpec.FormatOptions formatOptions = new FormatSpec.FormatOptions( - FormatSpec.MINIMUM_SUPPORTED_VERSION_OF_CODE_POINT_TABLE); + FormatSpec.MINIMUM_SUPPORTED_STATIC_VERSION); final FusionDictionary sourcedict = new FusionDictionary(new PtNodeArray(), BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion, formatOptions)); addUnigrams(words.size(), sourcedict, words, null /* shortcutMap */); @@ -359,11 +361,11 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final List<String> results = new ArrayList<>(); runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER, - BinaryDictUtils.VERSION2_OPTIONS); + BinaryDictUtils.STATIC_OPTIONS); runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER, - BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP); + BinaryDictUtils.DYNAMIC_OPTIONS_WITHOUT_TIMESTAMP); runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER, - BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP); + BinaryDictUtils.DYNAMIC_OPTIONS_WITH_TIMESTAMP); for (final String result : results) { Log.d(TAG, result); } @@ -373,11 +375,11 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final List<String> results = new ArrayList<>(); runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY, - BinaryDictUtils.VERSION2_OPTIONS); + BinaryDictUtils.STATIC_OPTIONS); runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY, - BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP); + BinaryDictUtils.DYNAMIC_OPTIONS_WITHOUT_TIMESTAMP); runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY, - BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP); + BinaryDictUtils.DYNAMIC_OPTIONS_WITH_TIMESTAMP); for (final String result : results) { Log.d(TAG, result); @@ -501,7 +503,7 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final ArrayList<String> results = new ArrayList<>(); runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER, - BinaryDictUtils.VERSION2_OPTIONS); + BinaryDictUtils.STATIC_OPTIONS); for (final String result : results) { Log.d(TAG, result); @@ -512,7 +514,7 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final ArrayList<String> results = new ArrayList<>(); runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY, - BinaryDictUtils.VERSION2_OPTIONS); + BinaryDictUtils.STATIC_OPTIONS); for (final String result : results) { Log.d(TAG, result); @@ -604,11 +606,10 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { + " : " + outputOptions(bufferType, formatOptions)); // Test a word that isn't contained within the dictionary. - final Random random = new Random((int)System.currentTimeMillis()); final int[] codePointSet = CodePointUtils.generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, - random); + mRandom); for (int i = 0; i < 1000; ++i) { - final String word = CodePointUtils.generateWord(random, codePointSet); + final String word = CodePointUtils.generateWord(mRandom, codePointSet); if (sWords.indexOf(word) != -1) continue; checkGetTerminalPosition(dictDecoder, word, false); } @@ -623,9 +624,9 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { final ArrayList<String> results = new ArrayList<>(); runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY, - BinaryDictUtils.VERSION2_OPTIONS); + BinaryDictUtils.STATIC_OPTIONS); runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER, - BinaryDictUtils.VERSION2_OPTIONS); + BinaryDictUtils.STATIC_OPTIONS); for (final String result : results) { Log.d(TAG, result); @@ -633,7 +634,7 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { } public void testVer2DictGetWordProperty() { - final FormatOptions formatOptions = BinaryDictUtils.VERSION2_OPTIONS; + final FormatOptions formatOptions = BinaryDictUtils.STATIC_OPTIONS; final ArrayList<String> words = sWords; final HashMap<String, List<String>> shortcuts = sShortcuts; final String dictName = "testGetWordProperty"; @@ -669,7 +670,7 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { } public void testVer2DictIteration() { - final FormatOptions formatOptions = BinaryDictUtils.VERSION2_OPTIONS; + final FormatOptions formatOptions = BinaryDictUtils.STATIC_OPTIONS; final ArrayList<String> words = sWords; final HashMap<String, List<String>> shortcuts = sShortcuts; final SparseArray<List<Integer>> bigrams = sEmptyBigrams; diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java index 120b96bc6..be75565bb 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java @@ -17,11 +17,16 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.makedict.UnsupportedFormatException; + import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.HashMap; +import java.util.LinkedList; + +import javax.annotation.Nonnull; /** * Decodes binary files for a FusionDictionary. @@ -361,6 +366,43 @@ public final class BinaryDictDecoderUtils { } /** + * Helper method that brutally decodes a header from a byte array. + * + * @param headerBuffer a buffer containing the bytes of the header. + * @return a hashmap of the attributes stored in the header + */ + @Nonnull + public static HashMap<String, String> decodeHeaderAttributes(@Nonnull final byte[] headerBuffer) + throws UnsupportedFormatException { + final StringBuilder sb = new StringBuilder(); + final LinkedList<String> keyValues = new LinkedList<>(); + int index = 0; + while (index < headerBuffer.length) { + if (headerBuffer[index] == FormatSpec.PTNODE_CHARACTERS_TERMINATOR) { + keyValues.add(sb.toString()); + sb.setLength(0); + } else if (CharEncoding.fitsOnOneByte(headerBuffer[index] & 0xFF, + null /* codePointTable */)) { + sb.appendCodePoint(headerBuffer[index] & 0xFF); + } else { + sb.appendCodePoint(((headerBuffer[index] & 0xFF) << 16) + + ((headerBuffer[index + 1] & 0xFF) << 8) + + (headerBuffer[index + 2] & 0xFF)); + index += 2; + } + index += 1; + } + if ((keyValues.size() & 1) != 0) { + throw new UnsupportedFormatException("Odd number of attributes"); + } + final HashMap<String, String> attributes = new HashMap<>(); + for (int i = 0; i < keyValues.size(); i += 2) { + attributes.put(keyValues.get(i), keyValues.get(i + 1)); + } + return attributes; + } + + /** * Helper method to pass a file name instead of a File object to isBinaryDictionary. */ public static boolean isBinaryDictionary(final String filename) { diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java index 60e38250f..ce905c499 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java @@ -819,12 +819,18 @@ public class BinaryDictEncoderUtils { final ArrayList<Entry<Integer, Integer>> codePointOccurrenceArray) throws IOException, UnsupportedFormatException { final int version = formatOptions.mVersion; - if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION - || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) { + if ((version >= FormatSpec.MINIMUM_SUPPORTED_STATIC_VERSION && + version <= FormatSpec.MAXIMUM_SUPPORTED_STATIC_VERSION) || ( + version >= FormatSpec.MINIMUM_SUPPORTED_DYNAMIC_VERSION && + version <= FormatSpec.MAXIMUM_SUPPORTED_DYNAMIC_VERSION)) { + // Dictionary is valid + } else { throw new UnsupportedFormatException("Requested file format version " + version - + ", but this implementation only supports versions " - + FormatSpec.MINIMUM_SUPPORTED_VERSION + " through " - + FormatSpec.MAXIMUM_SUPPORTED_VERSION); + + ", but this implementation only supports static versions " + + FormatSpec.MINIMUM_SUPPORTED_STATIC_VERSION + " through " + + FormatSpec.MAXIMUM_SUPPORTED_STATIC_VERSION + " and dynamic versions " + + FormatSpec.MINIMUM_SUPPORTED_DYNAMIC_VERSION + " through " + + FormatSpec.MAXIMUM_SUPPORTED_DYNAMIC_VERSION); } ByteArrayOutputStream headerBuffer = new ByteArrayOutputStream(256); diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java index 8eabf749d..9c1e4cf84 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java @@ -28,13 +28,11 @@ public class BinaryDictUtils { public static final String TEST_DICT_FILE_EXTENSION = ".testDict"; - public static final FormatSpec.FormatOptions VERSION2_OPTIONS = - new FormatSpec.FormatOptions(FormatSpec.VERSION2); - public static final FormatSpec.FormatOptions VERSION201_OPTIONS = - new FormatSpec.FormatOptions(FormatSpec.VERSION201); - public static final FormatSpec.FormatOptions VERSION4_OPTIONS_WITHOUT_TIMESTAMP = + public static final FormatSpec.FormatOptions STATIC_OPTIONS = + new FormatSpec.FormatOptions(FormatSpec.VERSION202); + public static final FormatSpec.FormatOptions DYNAMIC_OPTIONS_WITHOUT_TIMESTAMP = new FormatSpec.FormatOptions(FormatSpec.VERSION4, false /* hasTimestamp */); - public static final FormatSpec.FormatOptions VERSION4_OPTIONS_WITH_TIMESTAMP = + public static final FormatSpec.FormatOptions DYNAMIC_OPTIONS_WITH_TIMESTAMP = new FormatSpec.FormatOptions(FormatSpec.VERSION4, true /* hasTimestamp */); public static DictionaryOptions makeDictionaryOptions(final String id, final String version, @@ -55,7 +53,8 @@ public class BinaryDictUtils { public static File getDictFile(final String name, final String version, final FormatOptions formatOptions, final File directory) { if (formatOptions.mVersion == FormatSpec.VERSION2 - || formatOptions.mVersion == FormatSpec.VERSION201) { + || formatOptions.mVersion == FormatSpec.VERSION201 + || formatOptions.mVersion == FormatSpec.VERSION202) { return new File(directory, name + "." + version + TEST_DICT_FILE_EXTENSION); } else if (formatOptions.mVersion == FormatSpec.VERSION4) { return new File(directory, name + "." + version); @@ -71,7 +70,7 @@ public class BinaryDictUtils { file.mkdir(); } return new Ver4DictEncoder(file); - } else if (formatOptions.mVersion == FormatSpec.VERSION2) { + } else if (formatOptions.mVersion == FormatSpec.VERSION202) { return new Ver2DictEncoder(file, Ver2DictEncoder.CODE_POINT_TABLE_OFF); } else { throw new RuntimeException("The format option has a wrong version : " diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java index 457e7af8e..5c261a94d 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java +++ b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java @@ -178,7 +178,8 @@ public class Ver2DictDecoder extends AbstractDictDecoder { throw new IOException("Cannot read the dictionary header."); } if (header.mFormatOptions.mVersion != FormatSpec.VERSION2 && - header.mFormatOptions.mVersion != FormatSpec.VERSION201) { + header.mFormatOptions.mVersion != FormatSpec.VERSION201 && + header.mFormatOptions.mVersion != FormatSpec.VERSION202) { throw new UnsupportedFormatException("File header has a wrong version : " + header.mFormatOptions.mVersion); } diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java index 2c2152be7..b52b8c485 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java +++ b/tests/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java @@ -124,7 +124,8 @@ public class Ver2DictEncoder implements DictEncoder { @Override public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions) throws IOException, UnsupportedFormatException { - if (formatOptions.mVersion > FormatSpec.VERSION201) { + // We no longer support anything but the latest version of v2. + if (formatOptions.mVersion != FormatSpec.VERSION202) { throw new UnsupportedFormatException( "The given format options has wrong version number : " + formatOptions.mVersion); diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java index 7e54ce986..63ea89c1d 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java +++ b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java @@ -18,7 +18,7 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.BinaryDictionary; -import com.android.inputmethod.latin.utils.FileUtils; +import com.android.inputmethod.latin.common.FileUtils; import java.io.File; import java.io.FileNotFoundException; diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java index 155421922..1e4bd768c 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java +++ b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java @@ -20,10 +20,10 @@ import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.NgramContext; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.LocaleUtils; import java.io.File; import java.io.IOException; diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java index 778f6e800..a84df28c9 100644 --- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java +++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin.personalization; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; @@ -23,9 +25,10 @@ import android.util.Log; import com.android.inputmethod.latin.ExpandableBinaryDictionary; import com.android.inputmethod.latin.NgramContext; import com.android.inputmethod.latin.NgramContext.WordInfo; +import com.android.inputmethod.latin.common.FileUtils; +import com.android.inputmethod.latin.settings.LocalSettingsConstants; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; import com.android.inputmethod.latin.utils.DistracterFilter; -import com.android.inputmethod.latin.utils.FileUtils; import java.io.File; import java.io.FilenameFilter; @@ -36,6 +39,8 @@ import java.util.Locale; import java.util.Random; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + /** * Unit tests for UserHistoryDictionary */ @@ -44,6 +49,7 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { private static final String TAG = UserHistoryDictionaryTests.class.getSimpleName(); private static final int WAIT_FOR_WRITING_FILE_IN_MILLISECONDS = 3000; private static final String TEST_LOCALE_PREFIX = "test_"; + private static final String TEST_ACCOUNT = "account@example.com"; private static final String[] CHARACTERS = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", @@ -52,15 +58,18 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { private int mCurrentTime = 0; + private SharedPreferences mPrefs; + private String mLastKnownAccount = null; + private void removeAllTestDictFiles() { final Locale dummyLocale = new Locale(TEST_LOCALE_PREFIX); - final String dictName = ExpandableBinaryDictionary.getDictName( - UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */); + final String dictName = UserHistoryDictionary.getUserHistoryDictName( + UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */, getContext()); final File dictFile = ExpandableBinaryDictionary.getDictFile( mContext, dictName, null /* dictFile */); final FilenameFilter filenameFilter = new FilenameFilter() { @Override - public boolean accept(File dir, String filename) { + public boolean accept(final File dir, final String filename) { return filename.startsWith(UserHistoryDictionary.NAME + "." + TEST_LOCALE_PREFIX); } }; @@ -99,6 +108,12 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { @Override protected void setUp() throws Exception { super.setUp(); + + mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + // Keep track of the current account so that we restore it when the test finishes. + mLastKnownAccount = mPrefs.getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, null); + updateAccountName(TEST_ACCOUNT); + resetCurrentTimeForTestMode(); removeAllTestDictFiles(); } @@ -107,6 +122,10 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { protected void tearDown() throws Exception { removeAllTestDictFiles(); stopTestModeInNativeCode(); + + // Restore the account that was present before running the test. + updateAccountName(mLastKnownAccount); + super.tearDown(); } @@ -115,6 +134,14 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { setCurrentTimeForTestMode(mCurrentTime); } + private void updateAccountName(@Nullable final String accountName) { + if (accountName == null) { + mPrefs.edit().remove(LocalSettingsConstants.PREF_ACCOUNT_NAME).apply(); + } else { + mPrefs.edit().putString(LocalSettingsConstants.PREF_ACCOUNT_NAME, accountName).apply(); + } + } + private void forcePassingShortTime() { // 3 days. final int timeToElapse = (int)TimeUnit.DAYS.toSeconds(3); @@ -142,7 +169,7 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { */ private static String generateWord(final int value) { final int lengthOfChars = CHARACTERS.length; - StringBuilder builder = new StringBuilder(); + final StringBuilder builder = new StringBuilder(); long lvalue = Math.abs((long)value); while (lvalue > 0) { builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]); @@ -162,7 +189,7 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { private static void addToDict(final UserHistoryDictionary dict, final List<String> words, final int timestamp) { NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO; - for (String word : words) { + for (final String word : words) { UserHistoryDictionary.addToDictionary(dict, ngramContext, word, true, timestamp, DistracterFilter.EMPTY_DISTRACTER_FILTER); ngramContext = ngramContext.getNextNgramContext(new WordInfo(word)); @@ -204,12 +231,12 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { Log.d(TAG, "This test can be used for profiling."); Log.d(TAG, "Usage: please set UserHistoryDictionary.PROFILE_SAVE_RESTORE to true."); final Locale dummyLocale = getDummyLocale("random_words"); - final String dictName = ExpandableBinaryDictionary.getDictName( - UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */); + final String dictName = UserHistoryDictionary.getUserHistoryDictName( + UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */, getContext()); final File dictFile = ExpandableBinaryDictionary.getDictFile( mContext, dictName, null /* dictFile */); final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary( - getContext(), dummyLocale); + getContext(), dummyLocale, TEST_ACCOUNT); final int numberOfWords = 1000; final Random random = new Random(123456); @@ -232,12 +259,12 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { // Create filename suffixes for this test. for (int i = 0; i < numberOfLanguages; i++) { final Locale dummyLocale = getDummyLocale("switching_languages" + i); - final String dictName = ExpandableBinaryDictionary.getDictName( - UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */); + final String dictName = UserHistoryDictionary.getUserHistoryDictName( + UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */, getContext()); dictFiles[i] = ExpandableBinaryDictionary.getDictFile( mContext, dictName, null /* dictFile */); dicts[i] = PersonalizationHelper.getUserHistoryDictionary(getContext(), - dummyLocale); + dummyLocale, TEST_ACCOUNT); clearHistory(dicts[i]); } @@ -262,14 +289,14 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { public void testAddManyWords() { final Locale dummyLocale = getDummyLocale("many_random_words"); - final String dictName = ExpandableBinaryDictionary.getDictName( - UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */); + final String dictName = UserHistoryDictionary.getUserHistoryDictName( + UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */, getContext()); final File dictFile = ExpandableBinaryDictionary.getDictFile( mContext, dictName, null /* dictFile */); final int numberOfWords = 10000; final Random random = new Random(123456); final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary( - getContext(), dummyLocale); + getContext(), dummyLocale, TEST_ACCOUNT); clearHistory(dict); try { addAndWriteRandomWords(dict, numberOfWords, random, true /* checksContents */); @@ -281,7 +308,7 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { public void testDecaying() { final Locale dummyLocale = getDummyLocale("decaying"); final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary( - getContext(), dummyLocale); + getContext(), dummyLocale, TEST_ACCOUNT); final int numberOfWords = 5000; final Random random = new Random(123456); resetCurrentTimeForTestMode(); @@ -309,4 +336,9 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { assertFalse(dict.isInDictionary(word)); } } -} + + public void testRandomWords_NullAccount() { + updateAccountName(null); + testRandomWords(); + } +}
\ No newline at end of file diff --git a/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java index 131865ab2..f50b8e0b8 100644 --- a/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java @@ -20,6 +20,8 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.common.FileUtils; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.makedict.FormatSpec; diff --git a/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java index 4646a823d..9680d85b3 100644 --- a/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java @@ -21,8 +21,8 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.text.TextUtils; +import com.android.inputmethod.latin.common.LocaleUtils; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; -import com.android.inputmethod.latin.utils.LocaleUtils; import java.util.Locale; diff --git a/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java index a5979c3df..47fd5feaa 100644 --- a/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java @@ -19,6 +19,8 @@ package com.android.inputmethod.latin.utils; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import com.android.inputmethod.latin.common.CollectionUtils; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,14 +31,45 @@ import java.util.Collections; @SmallTest public class CollectionUtilsTests extends AndroidTestCase { /** + * Tests that {@link CollectionUtils#arrayAsList(Object[],int,int)} fails as expected + * with some invalid inputs. + */ + public void testArrayAsListFailure() { + final String[] array = { "0", "1" }; + // Negative start + try { + CollectionUtils.arrayAsList(array, -1, 1); + fail("Failed to catch start < 0"); + } catch (final IllegalArgumentException e) { + assertEquals("Invalid start: -1 end: 1 with array.length: 2", e.getMessage()); + } + // start > end + try { + CollectionUtils.arrayAsList(array, 1, -1); + fail("Failed to catch start > end"); + } catch (final IllegalArgumentException e) { + assertEquals("Invalid start: 1 end: -1 with array.length: 2", e.getMessage()); + } + // end > array.length + try { + CollectionUtils.arrayAsList(array, 1, 3); + fail("Failed to catch end > array.length"); + } catch (final IllegalArgumentException e) { + assertEquals("Invalid start: 1 end: 3 with array.length: 2", e.getMessage()); + } + } + + /** * Tests that {@link CollectionUtils#arrayAsList(Object[],int,int)} gives the expected * results for a few valid inputs. */ public void testArrayAsList() { - final String[] array = { "0", "1", "2", "3", "4" }; final ArrayList<String> empty = new ArrayList<>(); + assertEquals(empty, CollectionUtils.arrayAsList(new String[] { }, 0, 0)); + final String[] array = { "0", "1", "2", "3", "4" }; assertEquals(empty, CollectionUtils.arrayAsList(array, 0, 0)); assertEquals(empty, CollectionUtils.arrayAsList(array, 1, 1)); + assertEquals(empty, CollectionUtils.arrayAsList(array, array.length, array.length)); final ArrayList<String> expected123 = new ArrayList<>(Arrays.asList("1", "2", "3")); assertEquals(expected123, CollectionUtils.arrayAsList(array, 1, 4)); } diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java index 54f478f5a..03dcdfc78 100644 --- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java @@ -434,8 +434,8 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { // locale layout | display name // ------ -------------- - ---------------------- // sr south_slavic F Српски - // sr_ZZ serbian_qwertz F српски (латиница) - // sr_ZZ qwerty T српски (QWERTY) + // sr_ZZ serbian_qwertz F Српски (латиница) + // sr_ZZ qwerty T Српски (QWERTY) public void testSerbianLatinSubtypesInSerbianSystemLocale() { final RunInLocale<Void> tests = new RunInLocale<Void>() { @@ -445,12 +445,10 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR)); // These are preliminary subtypes and may not exist. if (SR_LATN != null) { - // TODO: Uncommented because of the current translation of these strings - // in Seriban are described in Latin script. -// assertEquals("sr_ZZ", "српски (латиница)", -// SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN)); -// assertEquals("sr_ZZ", "српски (QWERTY)", -// SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN_QWERTY)); + assertEquals("sr_ZZ", "Српски (латиница)", + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN)); + assertEquals("sr_ZZ", "Српски (QWERTY)", + SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN_QWERTY)); } return null; } diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk index 81c0706c1..1a9f029ae 100644 --- a/tools/dicttool/Android.mk +++ b/tools/dicttool/Android.mk @@ -42,23 +42,15 @@ LATINIME_TESTS_SRC_DIR := $(LATINIME_LOCAL_DIR)/tests/src/com/android/inputmetho # a significant part of the dependencies are mocked in the compat/ directory, with empty or # nearly-empty implementations, for parts that we don't use in Dicttool. LATINIME_SRC_FILES_FOR_DICTTOOL := \ - event/Combiner.java \ - event/Event.java \ latin/BinaryDictionary.java \ latin/DicTraverseSession.java \ latin/Dictionary.java \ - latin/LastComposedWord.java \ latin/NgramContext.java \ latin/SuggestedWords.java \ - latin/WordComposer.java \ - latin/settings/NativeSuggestOptions.java \ latin/settings/SettingsValuesForSuggestion.java \ latin/utils/BinaryDictionaryUtils.java \ latin/utils/CombinedFormatUtils.java \ - latin/utils/CoordinateUtils.java \ - latin/utils/FileUtils.java \ - latin/utils/JniUtils.java \ - latin/utils/LocaleUtils.java + latin/utils/JniUtils.java LATINIME_OVERRIDABLE_SRC_FILES_FOR_DICTTOOL := \ latin/define/DebugFlags.java diff --git a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java b/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java deleted file mode 100644 index c4457a1b7..000000000 --- a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.event; - -import java.util.ArrayList; - -/** - * Compatibility class that stands in for the combiner chain in LatinIME. - * - * This is not used by dicttool, it's just needed by the dependency chain. - */ -// TODO: there should not be a dependency to this in dicttool, so there -// should be a sensible way to separate them cleanly. -public class CombinerChain { - private StringBuilder mComposingWord; - public CombinerChain(final String initialText, final Combiner... combinerList) { - mComposingWord = new StringBuilder(initialText); - } - - public Event processEvent(final ArrayList<Event> previousEvents, final Event newEvent) { - return newEvent; - } - - public void applyProcessedEvent(final Event event) { - mComposingWord.append(event.getTextToCommit()); - } - - public CharSequence getComposingWordWithCombiningFeedback() { - return mComposingWord; - } - - public void reset() { - mComposingWord.setLength(0); - } - - public static Combiner[] createCombiners(final String spec) { - // Dicttool never uses a combiner at all, so we just return a zero-sized array. - return new Combiner[0]; - } -} diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java index 1c5dfa9fb..84c3956f7 100644 --- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java @@ -19,6 +19,10 @@ package com.android.inputmethod.latin.dicttool; import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils; import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; import com.android.inputmethod.latin.makedict.DictDecoder; +import com.android.inputmethod.latin.makedict.DictionaryHeader; +import com.android.inputmethod.latin.makedict.FormatSpec; +import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions; +import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; @@ -27,12 +31,18 @@ import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashMap; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Class grouping utilities for offline dictionary making. @@ -44,26 +54,27 @@ public final class BinaryDictOffdeviceUtils { // Prefix and suffix are arbitrary, the values do not really matter private final static String PREFIX = "dicttool"; private final static String SUFFIX = ".tmp"; - private final static int COPY_BUFFER_SIZE = 8192; - public static class DecoderChainSpec { + public static class DecoderChainSpec<T> { public final static int COMPRESSION = 1; public final static int ENCRYPTION = 2; - private final static int MAX_DECODE_DEPTH = 4; - final int[] mDecoderSpec; - File mFile; + private final static int[][] VALID_DECODER_CHAINS = { + { }, { COMPRESSION }, { ENCRYPTION, COMPRESSION } + }; + + private final int mDecoderSpecIndex; + public T mResult; public DecoderChainSpec() { - mDecoderSpec = new int[0]; - mFile = null; + mDecoderSpecIndex = 0; + mResult = null; } - public DecoderChainSpec(final DecoderChainSpec src, final int newStep) { - mDecoderSpec = Arrays.copyOf(src.mDecoderSpec, src.mDecoderSpec.length + 1); - mDecoderSpec[src.mDecoderSpec.length] = newStep; - mFile = src.mFile; + private DecoderChainSpec(final DecoderChainSpec<T> src) { + mDecoderSpecIndex = src.mDecoderSpecIndex + 1; + mResult = src.mResult; } private String getStepDescription(final int step) { @@ -79,110 +90,177 @@ public final class BinaryDictOffdeviceUtils { public String describeChain() { final StringBuilder s = new StringBuilder("raw"); - for (final int step : mDecoderSpec) { + for (final int step : VALID_DECODER_CHAINS[mDecoderSpecIndex]) { s.append(" > "); s.append(getStepDescription(step)); } return s.toString(); } - } - public static void copy(final InputStream input, final OutputStream output) throws IOException { - final byte[] buffer = new byte[COPY_BUFFER_SIZE]; - for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) { - output.write(buffer, 0, readBytes); + /** + * Returns the next sequential spec. If exhausted, return null. + */ + public DecoderChainSpec next() { + if (mDecoderSpecIndex + 1 >= VALID_DECODER_CHAINS.length) { + return null; + } + return new DecoderChainSpec(this); + } + + public InputStream getStream(final File src) throws FileNotFoundException, IOException { + InputStream input = new BufferedInputStream(new FileInputStream(src)); + for (final int step : VALID_DECODER_CHAINS[mDecoderSpecIndex]) { + switch (step) { + case COMPRESSION: + input = Compress.getUncompressedStream(input); + break; + case ENCRYPTION: + input = Crypt.getDecryptedStream(input); + break; + } + } + return input; } } - /** - * Returns a decrypted/uncompressed dictionary. - * - * This will decrypt/uncompress any number of times as necessary until it finds the - * dictionary signature, and copy the decoded file to a temporary place. - * If this is not a dictionary, the method returns null. - */ - public static DecoderChainSpec getRawDictionaryOrNull(final File src) { - return getRawDictionaryOrNullInternal(new DecoderChainSpec(), src, 0); + public interface InputProcessor<T> { + @Nonnull + public T process(@Nonnull final InputStream input) + throws IOException, UnsupportedFormatException; } - private static DecoderChainSpec getRawDictionaryOrNullInternal( - final DecoderChainSpec spec, final File src, final int depth) { - // Unfortunately the decoding scheme we use can consider any data to be encrypted - // and will produce some output, meaning it's not possible to reliably detect encrypted - // data. Thus, some non-dictionary files (especially small) ones may successfully decrypt - // over and over, ending in a stack overflow. Hence we limit the depth at which we try - // decoding the file. - if (depth > DecoderChainSpec.MAX_DECODE_DEPTH) { - return null; + public static class CopyProcessor implements InputProcessor<File> { + @Override @Nonnull + public File process(@Nonnull final InputStream input) throws IOException, + UnsupportedFormatException { + final File dst = File.createTempFile(PREFIX, SUFFIX); + dst.deleteOnExit(); + try (final OutputStream output = new BufferedOutputStream(new FileOutputStream(dst))) { + copy(input, output); + output.flush(); + output.close(); + if (BinaryDictDecoderUtils.isBinaryDictionary(dst) + || CombinedInputOutput.isCombinedDictionary(dst.getAbsolutePath())) { + return dst; + } + } + throw new UnsupportedFormatException("Input stream not at the expected format"); } - if (BinaryDictDecoderUtils.isBinaryDictionary(src) - || CombinedInputOutput.isCombinedDictionary(src.getAbsolutePath())) { - spec.mFile = src; - return spec; + } + + public static class HeaderReaderProcessor implements InputProcessor<DictionaryHeader> { + // Arbitrarily limit the header length to 32k. Sounds like it would never be larger + // than this. Revisit this if needed later. + private final int MAX_HEADER_LENGTH = 32 * 1024; + @Override @Nonnull + public DictionaryHeader process(final InputStream input) throws IOException, + UnsupportedFormatException { + // Do everything as curtly and ad-hoc as possible for performance. + final byte[] tmpBuffer = new byte[12]; + if (tmpBuffer.length != input.read(tmpBuffer)) { + throw new UnsupportedFormatException("File too short, not a dictionary"); + } + // Ad-hoc check for the magic number. See FormatSpec.java as well as + // byte_array_utils.h and BinaryDictEncoderUtils#writeDictionaryHeader(). + final int MAGIC_NUMBER_START_OFFSET = 0; + final int VERSION_START_OFFSET = 4; + final int HEADER_SIZE_OFFSET = 8; + final int magicNumber = ((tmpBuffer[MAGIC_NUMBER_START_OFFSET] & 0xFF) << 24) + + ((tmpBuffer[MAGIC_NUMBER_START_OFFSET + 1] & 0xFF) << 16) + + ((tmpBuffer[MAGIC_NUMBER_START_OFFSET + 2] & 0xFF) << 8) + + (tmpBuffer[MAGIC_NUMBER_START_OFFSET + 3] & 0xFF); + if (magicNumber != FormatSpec.MAGIC_NUMBER) { + throw new UnsupportedFormatException("Wrong magic number"); + } + final int version = ((tmpBuffer[VERSION_START_OFFSET] & 0xFF) << 8) + + (tmpBuffer[VERSION_START_OFFSET + 1] & 0xFF); + if (version != FormatSpec.VERSION2 && version != FormatSpec.VERSION201 + && version != FormatSpec.VERSION202) { + throw new UnsupportedFormatException("Only versions 2, 201, 202 are supported"); + } + final int totalHeaderSize = ((tmpBuffer[HEADER_SIZE_OFFSET] & 0xFF) << 24) + + ((tmpBuffer[HEADER_SIZE_OFFSET + 1] & 0xFF) << 16) + + ((tmpBuffer[HEADER_SIZE_OFFSET + 2] & 0xFF) << 8) + + (tmpBuffer[HEADER_SIZE_OFFSET + 3] & 0xFF); + if (totalHeaderSize > MAX_HEADER_LENGTH) { + throw new UnsupportedFormatException("Header too large"); + } + final byte[] headerBuffer = new byte[totalHeaderSize - tmpBuffer.length]; + readStreamExhaustively(input, headerBuffer); + final HashMap<String, String> attributes = + BinaryDictDecoderUtils.decodeHeaderAttributes(headerBuffer); + return new DictionaryHeader(totalHeaderSize, new DictionaryOptions(attributes), + new FormatOptions(version, false /* hasTimestamp */)); } - // It's not a raw dictionary - try to see if it's compressed. - final File uncompressedFile = tryGetUncompressedFile(src); - if (null != uncompressedFile) { - final DecoderChainSpec newSpec = - getRawDictionaryOrNullInternal(spec, uncompressedFile, depth + 1); - if (null == newSpec) return null; - return new DecoderChainSpec(newSpec, DecoderChainSpec.COMPRESSION); + } + + private static void readStreamExhaustively(final InputStream inputStream, + final byte[] outBuffer) throws IOException, UnsupportedFormatException { + int readBytes = 0; + int readBytesLastCycle = -1; + while (readBytes != outBuffer.length) { + readBytesLastCycle = inputStream.read(outBuffer, readBytes, + outBuffer.length - readBytes); + if (readBytesLastCycle == -1) + throw new UnsupportedFormatException("File shorter than specified in the header" + + " (expected " + outBuffer.length + ", read " + readBytes + ")"); + readBytes += readBytesLastCycle; } - // It's not a compressed either - try to see if it's crypted. - final File decryptedFile = tryGetDecryptedFile(src); - if (null != decryptedFile) { - final DecoderChainSpec newSpec = - getRawDictionaryOrNullInternal(spec, decryptedFile, depth + 1); - if (null == newSpec) return null; - return new DecoderChainSpec(newSpec, DecoderChainSpec.ENCRYPTION); + } + + public static void copy(final InputStream input, final OutputStream output) throws IOException { + final byte[] buffer = new byte[COPY_BUFFER_SIZE]; + for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) { + output.write(buffer, 0, readBytes); } - return null; } - /* Try to uncompress the file passed as an argument. + /** + * Process a dictionary, decrypting/uncompressing it on the fly as necessary. * - * If the file can be uncompressed, the uncompressed version is returned. Otherwise, null - * is returned. + * This will execute the given processor repeatedly with the possible alternatives + * for dictionary format until the processor does not throw an exception. + * If the processor succeeds for none of the possible formats, the method returns null. */ - private static File tryGetUncompressedFile(final File src) { - try { - final File dst = File.createTempFile(PREFIX, SUFFIX); - dst.deleteOnExit(); - try ( - final InputStream input = Compress.getUncompressedStream( - new BufferedInputStream(new FileInputStream(src))); - final OutputStream output = new BufferedOutputStream(new FileOutputStream(dst)) - ) { - copy(input, output); - return dst; + @Nullable + public static <T> DecoderChainSpec<T> decodeDictionaryForProcess(@Nonnull final File src, + @Nonnull final InputProcessor<T> processor) { + @Nonnull DecoderChainSpec spec = new DecoderChainSpec(); + while (null != spec) { + try { + final InputStream input = spec.getStream(src); + spec.mResult = processor.process(input); + try { + input.close(); + } catch (IOException e) { + // CipherInputStream doesn't like being closed without having read the + // entire stream, for some reason. But we don't want to because it's a waste + // of resources. We really, really don't care about this. + // However on close() CipherInputStream does throw this exception, wrapped + // in an IOException so we need to catch it. + if (!(e.getCause() instanceof javax.crypto.BadPaddingException)) { + throw e; + } + } + return spec; + } catch (IOException | UnsupportedFormatException | ArrayIndexOutOfBoundsException e) { + // If the format is not the right one for this file, the processor will throw one + // of these exceptions. In our case, that means we should try the next spec, + // since it may still be at another format we haven't tried yet. + // TODO: stop using exceptions for this non-exceptional case. } - } catch (final IOException e) { - // Could not uncompress the file: presumably the file is simply not a compressed file - return null; + spec = spec.next(); } + return null; } - /* Try to decrypt the file passed as an argument. - * - * If the file can be decrypted, the decrypted version is returned. Otherwise, null - * is returned. + /** + * Get a decoder chain spec with a raw dictionary file. This makes a new file on the + * disk ready for any treatment the client wants. */ - private static File tryGetDecryptedFile(final File src) { - try { - final File dst = File.createTempFile(PREFIX, SUFFIX); - dst.deleteOnExit(); - try ( - final InputStream input = Crypt.getDecryptedStream( - new BufferedInputStream(new FileInputStream(src))); - final OutputStream output = new BufferedOutputStream(new FileOutputStream(dst)) - ) { - copy(input, output); - return dst; - } - } catch (final IOException e) { - // Could not decrypt the file: presumably the file is simply not a crypted file - return null; - } + @Nullable + public static DecoderChainSpec<File> getRawDictionaryOrNull(@Nonnull final File src) { + return decodeDictionaryForProcess(src, new CopyProcessor()); } static FusionDictionary getDictionary(final String filename, final boolean report) { @@ -192,28 +270,28 @@ public final class BinaryDictOffdeviceUtils { System.out.println("Size : " + file.length() + " bytes"); } try { - final DecoderChainSpec decodedSpec = getRawDictionaryOrNull(file); + final DecoderChainSpec<File> decodedSpec = getRawDictionaryOrNull(file); if (null == decodedSpec) { throw new RuntimeException("Does not seem to be a dictionary file " + filename); } - if (CombinedInputOutput.isCombinedDictionary(decodedSpec.mFile.getAbsolutePath())) { + if (CombinedInputOutput.isCombinedDictionary(decodedSpec.mResult.getAbsolutePath())) { if (report) { System.out.println("Format : Combined format"); System.out.println("Packaging : " + decodedSpec.describeChain()); - System.out.println("Uncompressed size : " + decodedSpec.mFile.length()); + System.out.println("Uncompressed size : " + decodedSpec.mResult.length()); } try (final BufferedReader reader = new BufferedReader( - new InputStreamReader(new FileInputStream(decodedSpec.mFile), "UTF-8"))) { + new InputStreamReader(new FileInputStream(decodedSpec.mResult), "UTF-8"))) { return CombinedInputOutput.readDictionaryCombined(reader); } } final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder( - decodedSpec.mFile, 0, decodedSpec.mFile.length(), + decodedSpec.mResult, 0, decodedSpec.mResult.length(), DictDecoder.USE_BYTEARRAY); if (report) { System.out.println("Format : Binary dictionary format"); System.out.println("Packaging : " + decodedSpec.describeChain()); - System.out.println("Uncompressed size : " + decodedSpec.mFile.length()); + System.out.println("Uncompressed size : " + decodedSpec.mResult.length()); } return dictDecoder.readDictionaryBinary(false /* deleteDictIfBroken */); } catch (final IOException | UnsupportedFormatException e) { diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java index 48d2e5922..955c5728c 100644 --- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java @@ -98,6 +98,7 @@ public class CombinedInputOutput { String word = null; ProbabilityInfo probabilityInfo = new ProbabilityInfo(0); boolean isNotAWord = false; + boolean isPossiblyOffensive = false; ArrayList<WeightedString> bigrams = new ArrayList<>(); ArrayList<WeightedString> shortcuts = new ArrayList<>(); while (null != (line = reader.readLine())) { @@ -106,7 +107,7 @@ public class CombinedInputOutput { if (args[0].matches(CombinedFormatUtils.WORD_TAG + "=.*")) { if (null != word) { dict.add(word, probabilityInfo, shortcuts.isEmpty() ? null : shortcuts, - isNotAWord, false /* isPossiblyOffensive */); + isNotAWord, isPossiblyOffensive); for (WeightedString s : bigrams) { dict.setBigram(word, s.mWord, s.mProbabilityInfo); } @@ -114,27 +115,37 @@ public class CombinedInputOutput { if (!shortcuts.isEmpty()) shortcuts = new ArrayList<>(); if (!bigrams.isEmpty()) bigrams = new ArrayList<>(); isNotAWord = false; + isPossiblyOffensive = false; for (String param : args) { final String params[] = param.split("=", 2); if (2 != params.length) throw new RuntimeException("Wrong format : " + line); - if (CombinedFormatUtils.WORD_TAG.equals(params[0])) { - word = params[1]; - } else if (CombinedFormatUtils.PROBABILITY_TAG.equals(params[0])) { - probabilityInfo = new ProbabilityInfo(Integer.parseInt(params[1]), - probabilityInfo.mTimestamp, probabilityInfo.mLevel, - probabilityInfo.mCount); - } else if (CombinedFormatUtils.HISTORICAL_INFO_TAG.equals(params[0])) { - final String[] historicalInfoParams = - params[1].split(CombinedFormatUtils.HISTORICAL_INFO_SEPARATOR); - if (historicalInfoParams.length != HISTORICAL_INFO_ELEMENT_COUNT) { - throw new RuntimeException("Wrong format (historical info) : " + line); - } - probabilityInfo = new ProbabilityInfo(probabilityInfo.mProbability, - Integer.parseInt(historicalInfoParams[0]), - Integer.parseInt(historicalInfoParams[1]), - Integer.parseInt(historicalInfoParams[2])); - } else if (CombinedFormatUtils.NOT_A_WORD_TAG.equals(params[0])) { - isNotAWord = "true".equals(params[1]); + switch (params[0]) { + case CombinedFormatUtils.WORD_TAG: + word = params[1]; + break; + case CombinedFormatUtils.PROBABILITY_TAG: + probabilityInfo = new ProbabilityInfo(Integer.parseInt(params[1]), + probabilityInfo.mTimestamp, probabilityInfo.mLevel, + probabilityInfo.mCount); + break; + case CombinedFormatUtils.HISTORICAL_INFO_TAG: + final String[] historicalInfoParams = params[1].split( + CombinedFormatUtils.HISTORICAL_INFO_SEPARATOR); + if (historicalInfoParams.length != HISTORICAL_INFO_ELEMENT_COUNT) { + throw new RuntimeException("Wrong format (historical info) : " + + line); + } + probabilityInfo = new ProbabilityInfo(probabilityInfo.mProbability, + Integer.parseInt(historicalInfoParams[0]), + Integer.parseInt(historicalInfoParams[1]), + Integer.parseInt(historicalInfoParams[2])); + break; + case CombinedFormatUtils.NOT_A_WORD_TAG: + isNotAWord = CombinedFormatUtils.isLiteralTrue(params[1]); + break; + case CombinedFormatUtils.POSSIBLY_OFFENSIVE_TAG: + isPossiblyOffensive = CombinedFormatUtils.isLiteralTrue(params[1]); + break; } } } else if (args[0].matches(CombinedFormatUtils.SHORTCUT_TAG + "=.*")) { @@ -190,7 +201,7 @@ public class CombinedInputOutput { } if (null != word) { dict.add(word, probabilityInfo, shortcuts.isEmpty() ? null : shortcuts, isNotAWord, - false /* isPossiblyOffensive */); + isPossiblyOffensive); for (WeightedString s : bigrams) { dict.setBigram(word, s.mWord, s.mProbabilityInfo); } diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java index 0d93c7fa9..8fdf7633f 100644 --- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java @@ -18,7 +18,9 @@ package com.android.inputmethod.latin.dicttool; public class CommandList { public static void populate() { + // TODO: Move some commands to native code. Dicttool.addCommand("info", Info.class); + Dicttool.addCommand("header", Header.class); Dicttool.addCommand("diff", Diff.class); Dicttool.addCommand("compress", Compress.Compressor.class); Dicttool.addCommand("uncompress", Compress.Uncompressor.class); diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java index 8f9e4a3a6..6187853c8 100644 --- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java @@ -120,7 +120,7 @@ public class DictionaryMaker { String inputCombined = null; String outputBinary = null; String outputCombined = null; - int outputBinaryFormatVersion = FormatSpec.VERSION201; // the default version is 201. + int outputBinaryFormatVersion = FormatSpec.VERSION202; // the default version is 202. // Don't use code point table by default. int codePointTableMode = Ver2DictEncoder.CODE_POINT_TABLE_OFF; diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java new file mode 100644 index 000000000..ba96c0aeb --- /dev/null +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.dicttool; + +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.dicttool.BinaryDictOffdeviceUtils.DecoderChainSpec; +import com.android.inputmethod.latin.makedict.DictionaryHeader; +import com.android.inputmethod.latin.makedict.UnsupportedFormatException; + +import java.io.File; +import java.util.Arrays; +import java.util.Locale; + +public class Header extends Dicttool.Command { + public static final String COMMAND = "header"; + + public Header() { + } + + @Override + public String getHelp() { + return COMMAND + " <filename>: prints the header contents of a dictionary file"; + } + + @Override + public void run() throws UnsupportedFormatException { + final boolean plumbing; + if (mArgs.length > 0 && "-p".equals(mArgs[0])) { + plumbing = true; + mArgs = Arrays.copyOfRange(mArgs, 1, mArgs.length); + } else { + plumbing = false; + } + if (mArgs.length < 1) { + throw new RuntimeException("Not enough arguments for command " + COMMAND); + } + final String filename = mArgs[0]; + final File dictFile = new File(filename); + final DecoderChainSpec<DictionaryHeader> spec = + BinaryDictOffdeviceUtils.decodeDictionaryForProcess(dictFile, + new BinaryDictOffdeviceUtils.HeaderReaderProcessor()); + if (null == spec) { + throw new UnsupportedFormatException(filename + + " doesn't seem to be a valid version 2 dictionary file"); + } + + final DictionaryHeader header = spec.mResult; + System.out.println("Dictionary : " + dictFile.getAbsolutePath()); + System.out.println("Size : " + dictFile.length() + " bytes"); + System.out.println("Format : Binary dictionary format"); + System.out.println("Format version : " + header.mFormatOptions.mVersion); + System.out.println("Packaging : " + spec.describeChain()); + System.out.println("Header attributes :"); + System.out.print(header.mDictionaryOptions.toString(2 /* indentCount */, plumbing)); + } +} diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java index 47ea70629..3efa10a80 100644 --- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin.dicttool; +import com.android.inputmethod.latin.makedict.DictionaryHeader; + import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; @@ -77,16 +79,16 @@ public class Package { if (mArgs.length != 2) { throw new RuntimeException("Too many/too few arguments for command " + COMMAND); } - final BinaryDictOffdeviceUtils.DecoderChainSpec decodedSpec = - BinaryDictOffdeviceUtils.getRawDictionaryOrNull(new File(mArgs[0])); + final BinaryDictOffdeviceUtils.DecoderChainSpec<DictionaryHeader> decodedSpec = + BinaryDictOffdeviceUtils.decodeDictionaryForProcess(new File(mArgs[0]), + new BinaryDictOffdeviceUtils.HeaderReaderProcessor()); if (null == decodedSpec) { System.out.println(mArgs[0] + " does not seem to be a dictionary"); return; } System.out.println("Packaging : " + decodedSpec.describeChain()); - System.out.println("Uncompressed size : " + decodedSpec.mFile.length()); try ( - final InputStream input = getFileInputStream(decodedSpec.mFile); + final InputStream input = decodedSpec.getStream(new File(mArgs[0])); final OutputStream output = new BufferedOutputStream( getFileOutputStreamOrStdOut(mArgs[1])) ) { diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java index b6383d788..e2dd5199b 100644 --- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java @@ -16,10 +16,10 @@ package com.android.inputmethod.latin.dicttool; +import com.android.inputmethod.latin.common.FileUtils; import com.android.inputmethod.latin.makedict.BinaryDictDecoderEncoderTests; import com.android.inputmethod.latin.makedict.BinaryDictEncoderFlattenTreeTests; import com.android.inputmethod.latin.makedict.FusionDictionaryTest; -import com.android.inputmethod.latin.utils.FileUtils; import java.io.File; import java.io.IOException; diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java index 6cdbff7e5..ea9d4cc19 100644 --- a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java +++ b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java @@ -16,10 +16,17 @@ package com.android.inputmethod.latin.dicttool; +import com.android.inputmethod.latin.common.CodePointUtils; +import com.android.inputmethod.latin.dicttool.BinaryDictOffdeviceUtils; +import com.android.inputmethod.latin.dicttool.Compress; +import com.android.inputmethod.latin.dicttool.Crypt; +import com.android.inputmethod.latin.dicttool.BinaryDictOffdeviceUtils.DecoderChainSpec; import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; +import com.android.inputmethod.latin.makedict.BinaryDictUtils; import com.android.inputmethod.latin.makedict.DictDecoder; import com.android.inputmethod.latin.makedict.DictEncoder; import com.android.inputmethod.latin.makedict.DictionaryHeader; +import com.android.inputmethod.latin.makedict.FormatSpec; import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary; @@ -35,13 +42,37 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; /** * Unit tests for BinaryDictOffdeviceUtils */ public class BinaryDictOffdeviceUtilsTests extends TestCase { private static final int TEST_FREQ = 37; // Some arbitrary value unlikely to happen by chance + private static final int CODE_POINT_SET_SIZE = 300; + final Random mRandom; + private static final ArrayList<String> sWords = new ArrayList<>(); + + public BinaryDictOffdeviceUtilsTests(final long seed, final int maxUnigrams) { + super(); + mRandom = new Random(seed); + sWords.clear(); + generateWords(maxUnigrams, mRandom); + } + + private static void generateWords(final int maxUnigrams, final Random random) { + final int[] codePointSet = CodePointUtils.generateCodePointSet( + CODE_POINT_SET_SIZE, random); + final Set<String> wordSet = new HashSet<>(); + while (wordSet.size() < maxUnigrams) { + wordSet.add(CodePointUtils.generateWord(random, codePointSet)); + } + sWords.addAll(wordSet); + } public void testGetRawDictWorks() throws IOException, UnsupportedFormatException { final String VERSION = "1"; @@ -68,23 +99,17 @@ public class BinaryDictOffdeviceUtilsTests extends TestCase { final File dst = File.createTempFile("testGetRawDict", ".tmp"); dst.deleteOnExit(); try (final OutputStream out = Compress.getCompressedStream( - Compress.getCompressedStream( - Compress.getCompressedStream( - new BufferedOutputStream(new FileOutputStream(dst)))))) { + new BufferedOutputStream(new FileOutputStream(dst)))) { final DictEncoder dictEncoder = new Ver2DictEncoder(out); - dictEncoder.writeDictionary(dict, new FormatOptions(2, false)); + dictEncoder.writeDictionary(dict, new FormatOptions(FormatSpec.VERSION202, false)); } // Test for an actually compressed dictionary and its contents - final BinaryDictOffdeviceUtils.DecoderChainSpec decodeSpec = + final BinaryDictOffdeviceUtils.DecoderChainSpec<File> decodeSpec = BinaryDictOffdeviceUtils.getRawDictionaryOrNull(dst); - for (final int step : decodeSpec.mDecoderSpec) { - assertEquals("Wrong decode spec", - BinaryDictOffdeviceUtils.DecoderChainSpec.COMPRESSION, step); - } - assertEquals("Wrong decode spec", 3, decodeSpec.mDecoderSpec.length); - final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder(decodeSpec.mFile, 0, - decodeSpec.mFile.length()); + assertEquals("Wrong decode spec", "raw > compression", decodeSpec.describeChain()); + final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder(decodeSpec.mResult, 0, + decodeSpec.mResult.length()); final FusionDictionary resultDict = dictDecoder.readDictionaryBinary(false /* deleteDictIfBroken */); assertEquals("Wrong version attribute", VERSION, resultDict.mOptions.mAttributes.get( @@ -125,4 +150,64 @@ public class BinaryDictOffdeviceUtilsTests extends TestCase { assertNull("Wrongly identified data file", BinaryDictOffdeviceUtils.getRawDictionaryOrNull(gzDst)); } + + public void runTestHeaderReaderProcessorWithOneSpec(final boolean compress, final boolean crypt) + throws IOException, UnsupportedFormatException { + final String dictName = "testHeaderReaderProcessor"; + final String dictVersion = Long.toString(System.currentTimeMillis()); + final FormatOptions formatOptions = BinaryDictUtils.STATIC_OPTIONS; + final int MAX_NUMBER_OF_OPTIONS_TO_ADD = 5; + final HashMap<String, String> options = new HashMap<>(); + // Required attributes + options.put("dictionary", "main:en_US"); + options.put("locale", "en_US"); + options.put("version", Integer.toString(mRandom.nextInt())); + // Add some random options for test + final int numberOfOptionsToAdd = mRandom.nextInt() % (MAX_NUMBER_OF_OPTIONS_TO_ADD + 1); + for (int i = 0; i < numberOfOptionsToAdd; ++i) { + options.put(sWords.get(2 * i), sWords.get(2 * 1 + 1)); + } + final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), + new DictionaryOptions(options)); + + for (int i = 0; i < sWords.size(); ++i) { + final String word = sWords.get(i); + dict.add(word, new ProbabilityInfo(TEST_FREQ), null /* shortcuts */, + false /* isNotAWord */, false /* isPossiblyOffensive */); + } + + File file = File.createTempFile(dictName, ".tmp"); + final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions); + dictEncoder.writeDictionary(dict, formatOptions); + + if (compress) { + final File rawFile = file; + file = File.createTempFile(dictName + ".compress", ".tmp"); + final Compress.Compressor compressCommand = new Compress.Compressor(); + compressCommand.setArgs(new String[] { rawFile.getPath(), file.getPath() }); + compressCommand.run(); + } + if (crypt) { + final File rawFile = file; + file = File.createTempFile(dictName + ".crypt", ".tmp"); + final Crypt.Encrypter cryptCommand = new Crypt.Encrypter(); + cryptCommand.setArgs(new String[] { rawFile.getPath(), file.getPath() }); + cryptCommand.run(); + } + + final DecoderChainSpec<DictionaryHeader> spec = + BinaryDictOffdeviceUtils.decodeDictionaryForProcess(file, + new BinaryDictOffdeviceUtils.HeaderReaderProcessor()); + assertNotNull("Can't decode a dictionary we just wrote : " + file, spec); + final DictionaryHeader header = spec.mResult; + assertEquals("raw" + (crypt ? " > encryption" : "") + (compress ? " > compression" : ""), + spec.describeChain()); + assertEquals(header.mDictionaryOptions.mAttributes, options); + } + + public void testHeaderReaderProcessor() throws IOException, UnsupportedFormatException { + runTestHeaderReaderProcessorWithOneSpec(false /* compress */, false /* crypt */); + runTestHeaderReaderProcessorWithOneSpec(true /* compress */, false /* crypt */); + runTestHeaderReaderProcessorWithOneSpec(true /* compress */, true /* crypt */); + } } |