diff options
Diffstat (limited to '')
-rw-r--r-- | common/Android.mk | 28 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/annotations/ExternallyReferenced.java (renamed from java/src/com/android/inputmethod/annotations/ExternallyReferenced.java) | 0 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/annotations/UsedForTesting.java (renamed from java/src/com/android/inputmethod/annotations/UsedForTesting.java) | 0 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/CodePointUtils.java (renamed from tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java) | 28 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/CollectionUtils.java | 66 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/ComposedData.java | 66 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/Constants.java (renamed from java/src/com/android/inputmethod/latin/Constants.java) | 64 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/CoordinateUtils.java (renamed from java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java) | 37 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/FileUtils.java (renamed from java/src/com/android/inputmethod/latin/utils/FileUtils.java) | 2 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/InputPointers.java (renamed from java/src/com/android/inputmethod/latin/InputPointers.java) | 65 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/LocaleUtils.java (renamed from java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java) | 84 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/NativeSuggestOptions.java (renamed from java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java) | 27 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/ResizableIntArray.java (renamed from java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java) | 15 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/StringUtils.java (renamed from java/src/com/android/inputmethod/latin/utils/StringUtils.java) | 336 | ||||
-rw-r--r-- | common/src/com/android/inputmethod/latin/common/UnicodeSurrogate.java | 38 |
15 files changed, 574 insertions, 282 deletions
diff --git a/common/Android.mk b/common/Android.mk new file mode 100644 index 000000000..132a22358 --- /dev/null +++ b/common/Android.mk @@ -0,0 +1,28 @@ +# 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. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := latinime-common +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES := jsr305 +LOCAL_SDK_VERSION := 21 +include $(BUILD_STATIC_JAVA_LIBRARY) + +# Also build a host side library +include $(CLEAR_VARS) +LOCAL_MODULE := latinime-common-host +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES := jsr305lib +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java b/common/src/com/android/inputmethod/annotations/ExternallyReferenced.java index ea5f12ce2..ea5f12ce2 100644 --- a/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java +++ b/common/src/com/android/inputmethod/annotations/ExternallyReferenced.java diff --git a/java/src/com/android/inputmethod/annotations/UsedForTesting.java b/common/src/com/android/inputmethod/annotations/UsedForTesting.java index 2ada091e4..2ada091e4 100644 --- a/java/src/com/android/inputmethod/annotations/UsedForTesting.java +++ b/common/src/com/android/inputmethod/annotations/UsedForTesting.java diff --git a/tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java b/common/src/com/android/inputmethod/latin/common/CodePointUtils.java index a270ee774..ec59de850 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java +++ b/common/src/com/android/inputmethod/latin/common/CodePointUtils.java @@ -14,11 +14,17 @@ * limitations under the License. */ -package com.android.inputmethod.latin.makedict; +package com.android.inputmethod.latin.common; + +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 public class CodePointUtils { private CodePointUtils() { // This utility class is not publicly instantiable. @@ -60,17 +66,24 @@ public class CodePointUtils { 0x00FF /* LATIN SMALL LETTER Y WITH DIAERESIS */ }; - public static int[] generateCodePointSet(final int codePointSetSize, final Random random) { + @UsedForTesting + @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; } @@ -80,8 +93,11 @@ public class CodePointUtils { /** * Generates a random word. */ - public static String generateWord(final Random random, final int[] codePointSet) { - StringBuilder builder = new StringBuilder(); + @UsedForTesting + @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/common/src/com/android/inputmethod/latin/common/CollectionUtils.java b/common/src/com/android/inputmethod/latin/common/CollectionUtils.java new file mode 100644 index 000000000..48df413fd --- /dev/null +++ b/common/src/com/android/inputmethod/latin/common/CollectionUtils.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 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 com.android.inputmethod.annotations.UsedForTesting; + +import java.util.ArrayList; +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("Invalid start: " + start + " end: " + end + + " with array.length: " + array.length); + } + + final ArrayList<E> list = new ArrayList<>(end - start); + for (int i = start; i < end; i++) { + list.add(array[i]); + } + return list; + } + + /** + * Tests whether c contains no elements, true if c is null or c is empty. + * @param c Collection to test. + * @return Whether c contains no elements. + */ + @UsedForTesting + public static boolean isNullOrEmpty(@Nullable final Collection<?> c) { + return c == null || c.isEmpty(); + } +} 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/java/src/com/android/inputmethod/latin/Constants.java b/common/src/com/android/inputmethod/latin/common/Constants.java index 43af66eb7..b491c8cfd 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/common/src/com/android/inputmethod/latin/common/Constants.java @@ -14,9 +14,14 @@ * limitations under the License. */ -package com.android.inputmethod.latin; +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 { /** * The alpha value for fully opaque. @@ -57,6 +62,13 @@ public final class Constants { @SuppressWarnings("dep-ann") public static final String FORCE_ASCII = "forceAscii"; + /** + * The private IME option used to suppress the floating gesture preview for a given text + * field. This overrides the corresponding keyboard settings preference. + * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled} + */ + public static final String NO_FLOATING_GESTURE_PREVIEW = "noGestureFloatingPreview"; + private ImeOption() { // This utility class is not publicly instantiable. } @@ -152,7 +164,6 @@ public final class Constants { // TODO: replace the following constants with state in InputTransaction? public static final int NOT_A_COORDINATE = -1; public static final int SUGGESTION_STRIP_COORDINATE = -2; - public static final int SPELL_CHECKER_COORDINATE = -3; public static final int EXTERNAL_KEYBOARD_COORDINATE = -4; // A hint on how many characters to cache from the TextView. A good value of this is given by @@ -163,13 +174,6 @@ public final class Constants { // right for this. public static final int MAX_CHARACTERS_FOR_RECAPITALIZATION = 1024 * 100; - // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h - public static final int DICTIONARY_MAX_WORD_LENGTH = 48; - - // (MAX_PREV_WORD_COUNT_FOR_N_GRAM + 1)-gram is supported in Java side. Needs to modify - // MAX_PREV_WORD_COUNT_FOR_N_GRAM in native/jni/src/defines.h for suggestions. - public static final int MAX_PREV_WORD_COUNT_FOR_N_GRAM = 2; - // Key events coming any faster than this are long-presses. public static final int LONG_PRESS_MILLISECONDS = 200; // TODO: Set this value appropriately. @@ -203,8 +207,6 @@ public final class Constants { public static final int CODE_DASH = '-'; public static final int CODE_SINGLE_QUOTE = '\''; public static final int CODE_DOUBLE_QUOTE = '"'; - public static final int CODE_QUESTION_MARK = '?'; - public static final int CODE_EXCLAMATION_MARK = '!'; public static final int CODE_SLASH = '/'; public static final int CODE_BACKSLASH = '\\'; public static final int CODE_VERTICAL_BAR = '|'; @@ -217,15 +219,17 @@ public final class Constants { public static final int CODE_CLOSING_ANGLE_BRACKET = '>'; public static final int CODE_INVERTED_QUESTION_MARK = 0xBF; // ¿ public static final int CODE_INVERTED_EXCLAMATION_MARK = 0xA1; // ¡ + public static final int CODE_GRAVE_ACCENT = '`'; + public static final int CODE_CIRCUMFLEX_ACCENT = '^'; + public static final int CODE_TILDE = '~'; public static final String REGEXP_PERIOD = "\\."; public static final String STRING_SPACE = " "; - public static final String STRING_PERIOD_AND_SPACE = ". "; /** * Special keys code. Must be negative. - * These should be aligned with {@link KeyboardCodesSet#ID_TO_NAME}, - * {@link KeyboardCodesSet#DEFAULT}, and {@link KeyboardCodesSet#RTL}. + * These should be aligned with constants in + * {@link com.android.inputmethod.keyboard.internal.KeyboardCodesSet}. */ public static final int CODE_SHIFT = -1; public static final int CODE_CAPSLOCK = -2; @@ -248,6 +252,7 @@ public final class Constants { return code >= CODE_SPACE; } + @Nonnull public static String printableCode(final int code) { switch (code) { case CODE_SHIFT: return "shift"; @@ -275,7 +280,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) { @@ -287,24 +293,42 @@ public final class Constants { return "[" + sb + "]"; } - public static final int MAX_INT_BIT_COUNT = 32; - /** * Screen metrics (a.k.a. Device form factor) constants of - * {@link R.integer#config_screen_metrics}. + * {@link com.android.inputmethod.latin.R.integer#config_screen_metrics}. */ public static final int SCREEN_METRICS_SMALL_PHONE = 0; public static final int SCREEN_METRICS_LARGE_PHONE = 1; public static final int SCREEN_METRICS_LARGE_TABLET = 2; public static final int SCREEN_METRICS_SMALL_TABLET = 3; + @UsedForTesting + public static boolean isPhone(final int screenMetrics) { + return screenMetrics == SCREEN_METRICS_SMALL_PHONE + || screenMetrics == SCREEN_METRICS_LARGE_PHONE; + } + + @UsedForTesting + public static boolean isTablet(final int screenMetrics) { + return screenMetrics == SCREEN_METRICS_SMALL_TABLET + || screenMetrics == SCREEN_METRICS_LARGE_TABLET; + } + /** * Default capacity of gesture points container. - * This constant is used by {@link BatchInputArbiter} and etc. to preallocate regions that - * contain gesture event points. + * This constant is used by {@link com.android.inputmethod.keyboard.internal.BatchInputArbiter} + * and etc. to preallocate regions that contain gesture event points. */ public static final int DEFAULT_GESTURE_POINTS_CAPACITY = 128; + public static final int MAX_IME_DECODER_RESULTS = 20; + public static final int DECODER_SCORE_SCALAR = 1000000; + public static final int DECODER_MAX_SCORE = 1000000000; + + public static final int EVENT_BACKSPACE = 1; + public static final int EVENT_REJECTION = 2; + public static final int EVENT_REVERT = 3; + private Constants() { // This utility class is not publicly instantiable. } diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/common/src/com/android/inputmethod/latin/common/CoordinateUtils.java index 87df013a6..031662411 100644 --- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java +++ b/common/src/com/android/inputmethod/latin/common/CoordinateUtils.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.inputmethod.latin.utils; +package com.android.inputmethod.latin.common; -import java.util.Arrays; +import javax.annotation.Nonnull; public final class CoordinateUtils { private static final int INDEX_X = 0; @@ -27,32 +27,35 @@ public final class CoordinateUtils { // This utility class is not publicly instantiable. } + @Nonnull public static int[] newInstance() { return new int[ELEMENT_SIZE]; } - public static int x(final int[] coords) { + public static int x(@Nonnull final int[] coords) { return coords[INDEX_X]; } - public static int y(final int[] coords) { + public static int y(@Nonnull final int[] coords) { return coords[INDEX_Y]; } - public static void set(final int[] coords, final int x, final int y) { + public static void set(@Nonnull final int[] coords, final int x, final int y) { coords[INDEX_X] = x; coords[INDEX_Y] = y; } - public static void copy(final int[] destination, final int[] source) { + public static void copy(@Nonnull final int[] destination, @Nonnull final int[] source) { destination[INDEX_X] = source[INDEX_X]; destination[INDEX_Y] = source[INDEX_Y]; } + @Nonnull public static int[] newCoordinateArray(final int arraySize) { return new int[ELEMENT_SIZE * arraySize]; } + @Nonnull public static int[] newCoordinateArray(final int arraySize, final int defaultX, final int defaultY) { final int[] result = new int[ELEMENT_SIZE * arraySize]; @@ -62,30 +65,30 @@ public final class CoordinateUtils { return result; } - public static int xFromArray(final int[] coordsArray, final int index) { + public static int xFromArray(@Nonnull final int[] coordsArray, final int index) { return coordsArray[ELEMENT_SIZE * index + INDEX_X]; } - public static int yFromArray(final int[] coordsArray, final int index) { + public static int yFromArray(@Nonnull final int[] coordsArray, final int index) { return coordsArray[ELEMENT_SIZE * index + INDEX_Y]; } - public static int[] coordinateFromArray(final int[] coordsArray, final int index) { - final int baseIndex = ELEMENT_SIZE * index; - return Arrays.copyOfRange(coordsArray, baseIndex, baseIndex + ELEMENT_SIZE); + @Nonnull + public static int[] coordinateFromArray(@Nonnull final int[] coordsArray, final int index) { + final int[] coords = newInstance(); + set(coords, xFromArray(coordsArray, index), yFromArray(coordsArray, index)); + return coords; } - public static void setXYInArray(final int[] coordsArray, final int index, + public static void setXYInArray(@Nonnull final int[] coordsArray, final int index, final int x, final int y) { final int baseIndex = ELEMENT_SIZE * index; coordsArray[baseIndex + INDEX_X] = x; coordsArray[baseIndex + INDEX_Y] = y; } - public static void setCoordinateInArray(final int[] coordsArray, final int index, - final int[] coords) { - final int baseIndex = ELEMENT_SIZE * index; - coordsArray[baseIndex + INDEX_X] = coords[INDEX_X]; - coordsArray[baseIndex + INDEX_Y] = coords[INDEX_Y]; + public static void setCoordinateInArray(@Nonnull final int[] coordsArray, final int index, + @Nonnull final int[] coords) { + setXYInArray(coordsArray, index, x(coords), y(coords)); } } 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/java/src/com/android/inputmethod/latin/InputPointers.java b/common/src/com/android/inputmethod/latin/common/InputPointers.java index 790e0d830..4b2ae7e76 100644 --- a/java/src/com/android/inputmethod/latin/InputPointers.java +++ b/common/src/com/android/inputmethod/latin/common/InputPointers.java @@ -14,18 +14,14 @@ * limitations under the License. */ -package com.android.inputmethod.latin; - -import android.util.Log; -import android.util.SparseIntArray; +package com.android.inputmethod.latin.common; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.define.DebugFlags; -import com.android.inputmethod.latin.utils.ResizableIntArray; + +import javax.annotation.Nonnull; // TODO: This class is not thread-safe. public final class InputPointers { - private static final String TAG = InputPointers.class.getSimpleName(); private static final boolean DEBUG_TIME = false; private final int mDefaultCapacity; @@ -34,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); @@ -57,32 +53,33 @@ 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); - if (DebugFlags.DEBUG_ENABLED || DEBUG_TIME) { + if (DEBUG_TIME) { fillWithLastTimeUntil(index); } mTimes.addAt(index, time); } @UsedForTesting - 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); @@ -99,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; } @@ -114,6 +112,7 @@ public final class InputPointers { * Shift to the left by elementCount, discarding elementCount pointers at the start. * @param elementCount how many elements to shift. */ + @UsedForTesting public void shift(final int elementCount) { mXCoordinates.shift(elementCount); mYCoordinates.shift(elementCount); @@ -133,24 +132,29 @@ 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(); } + /** + * Gets the time each point was registered, in milliseconds, relative to the first event in the + * sequence. + * @return The time each point was registered, in milliseconds, relative to the first event in + * the sequence. + */ + @Nonnull public int[] getTimes() { - if (DebugFlags.DEBUG_ENABLED || DEBUG_TIME) { - if (!isValidTimeStamps()) { - throw new RuntimeException("Time stamps are invalid."); - } - } return mTimes.getPrimitiveArray(); } @@ -159,25 +163,4 @@ public final class InputPointers { return "size=" + getPointerSize() + " id=" + mPointerIds + " time=" + mTimes + " x=" + mXCoordinates + " y=" + mYCoordinates; } - - private boolean isValidTimeStamps() { - final int[] times = mTimes.getPrimitiveArray(); - final int[] pointerIds = mPointerIds.getPrimitiveArray(); - final SparseIntArray lastTimeOfPointers = new SparseIntArray(); - final int size = getPointerSize(); - for (int i = 0; i < size; ++i) { - final int pointerId = pointerIds[i]; - final int time = times[i]; - final int lastTime = lastTimeOfPointers.get(pointerId, time); - if (time < lastTime) { - // dump - for (int j = 0; j < size; ++j) { - Log.d(TAG, "--- (" + j + ") " + times[j]); - } - return false; - } - lastTimeOfPointers.put(pointerId, time); - } - return true; - } } diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/common/src/com/android/inputmethod/latin/common/LocaleUtils.java index 4f0805c5c..d5878c024 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. * @@ -104,9 +104,10 @@ public final class LocaleUtils { * @param testedLocale the locale to test. * @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; + public static int getMatchLevel(@Nullable final String referenceLocale, + @Nullable final String testedLocale) { + 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 +161,50 @@ 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) - return null; + @Nonnull + public static Locale constructLocaleFromString(@Nonnull final String localeString) { 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 31a20c4db..d673442ca 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. @@ -22,10 +22,14 @@ public class NativeSuggestOptions { private static final int USE_FULL_EDIT_DISTANCE = 1; private static final int BLOCK_OFFENSIVE_WORDS = 2; private static final int SPACE_AWARE_GESTURE_ENABLED = 3; - private static final int OPTIONS_SIZE = 4; + 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() { + mOptions = new int[OPTIONS_SIZE]; + } public void setIsGesture(final boolean value) { setBooleanOption(IS_GESTURE, value); @@ -39,17 +43,10 @@ public class NativeSuggestOptions { setBooleanOption(BLOCK_OFFENSIVE_WORDS, value); } - public void setSpaceAwareGestureEnabled(final boolean value) { - setBooleanOption(SPACE_AWARE_GESTURE_ENABLED, value); - } - - public void setAdditionalFeaturesOptions(final int[] additionalOptions) { - if (additionalOptions == null) { - return; - } - for (int i = 0; i < additionalOptions.length; i++) { - setIntegerOption(OPTIONS_SIZE + i, additionalOptions[i]); - } + public void setWeightForLocale(final float value) { + // We're passing this option as a fixed point value, in thousands. This is decoded in + // native code by SuggestOptions#weightForLocale(). + setIntegerOption(WEIGHT_FOR_LOCALE_IN_THOUSANDS, (int) (value * 1000)); } public int[] getOptions() { diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/common/src/com/android/inputmethod/latin/common/ResizableIntArray.java index 64c9e2cff..77f5c4cd5 100644 --- a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java +++ b/common/src/com/android/inputmethod/latin/common/ResizableIntArray.java @@ -14,12 +14,17 @@ * limitations under the License. */ -package com.android.inputmethod.latin.utils; +package com.android.inputmethod.latin.common; + +import com.android.inputmethod.annotations.UsedForTesting; 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 +94,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 +115,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; } @@ -136,6 +142,7 @@ public final class ResizableIntArray { * Shift to the left by elementCount, discarding elementCount pointers at the start. * @param elementCount how many elements to shift. */ + @UsedForTesting public void shift(final int elementCount) { System.arraycopy(mArray, elementCount, mArray, 0, mLength - elementCount); mLength -= elementCount; diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/common/src/com/android/inputmethod/latin/common/StringUtils.java index 79128dbd2..572f0cd9b 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/common/src/com/android/inputmethod/latin/common/StringUtils.java @@ -14,27 +14,23 @@ * limitations under the License. */ -package com.android.inputmethod.latin.utils; - -import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; - -import android.text.Spanned; -import android.text.TextUtils; +package com.android.inputmethod.latin.common; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.Constants; import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class StringUtils { public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case public static final int CAPITALIZE_FIRST = 1; // First only public static final int CAPITALIZE_ALL = 2; // All caps + @Nonnull private static final String EMPTY_STRING = ""; private static final char CHAR_LINE_FEED = 0X000A; @@ -49,12 +45,77 @@ public final class StringUtils { // This utility class is not publicly instantiable. } - public static int codePointCount(final String text) { - if (TextUtils.isEmpty(text)) return 0; - return text.codePointCount(0, text.length()); + // Taken from android.text.TextUtils. We are extensively using this method in many places, + // some of which don't have the android libraries available. + /** + * Returns true if the string is null or 0-length. + * @param str the string to be examined + * @return true if str is null or zero length + */ + public static boolean isEmpty(@Nullable final CharSequence str) { + return (str == null || str.length() == 0); + } + + // Taken from android.text.TextUtils to cut the dependency to the Android framework. + /** + * Returns a string containing the tokens joined by delimiters. + * @param delimiter the delimiter + * @param tokens an array objects to be joined. Strings will be formed from + * the objects by calling object.toString(). + */ + @Nonnull + public static String join(@Nonnull final CharSequence delimiter, + @Nonnull final Iterable<?> tokens) { + final StringBuilder sb = new StringBuilder(); + boolean firstTime = true; + for (final Object token: tokens) { + if (firstTime) { + firstTime = false; + } else { + sb.append(delimiter); + } + sb.append(token); + } + return sb.toString(); } - public static String newSingleCodePointString(int codePoint) { + // Taken from android.text.TextUtils to cut the dependency to the Android framework. + /** + * Returns true if a and b are equal, including if they are both null. + * <p><i>Note: In platform versions 1.1 and earlier, this method only worked well if + * both the arguments were instances of String.</i></p> + * @param a first CharSequence to check + * @param b second CharSequence to check + * @return true if a and b are equal + */ + public static boolean equals(@Nullable final CharSequence a, @Nullable final CharSequence b) { + if (a == b) { + return true; + } + final int length; + if (a != null && b != null && (length = a.length()) == b.length()) { + if (a instanceof String && b instanceof String) { + return a.equals(b); + } + for (int i = 0; i < length; i++) { + if (a.charAt(i) != b.charAt(i)) { + return false; + } + } + return true; + } + return false; + } + + public static int codePointCount(@Nullable final CharSequence text) { + if (isEmpty(text)) { + return 0; + } + return Character.codePointCount(text, 0, text.length()); + } + + @Nonnull + public static String newSingleCodePointString(final int codePoint) { if (Character.charCount(codePoint) == 1) { // Optimization: avoid creating a temporary array for characters that are // represented by a single char value @@ -64,9 +125,12 @@ public final class StringUtils { return new String(Character.toChars(codePoint)); } - public static boolean containsInArray(final String text, final String[] array) { + public static boolean containsInArray(@Nonnull final String text, + @Nonnull final String[] array) { for (final String element : array) { - if (text.equals(element)) return true; + if (text.equals(element)) { + return true; + } } return false; } @@ -76,19 +140,21 @@ public final class StringUtils { * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain * a comma character in it. */ + @Nonnull private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ","; - public static boolean containsInCommaSplittableText(final String text, - final String extraValues) { - if (TextUtils.isEmpty(extraValues)) { + public static boolean containsInCommaSplittableText(@Nonnull final String text, + @Nullable final String extraValues) { + if (isEmpty(extraValues)) { return false; } return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT)); } - public static String removeFromCommaSplittableTextIfExists(final String text, - final String extraValues) { - if (TextUtils.isEmpty(extraValues)) { + @Nonnull + public static String removeFromCommaSplittableTextIfExists(@Nonnull final String text, + @Nullable final String extraValues) { + if (isEmpty(extraValues)) { return EMPTY_STRING; } final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT); @@ -101,7 +167,7 @@ public final class StringUtils { result.add(element); } } - return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result); + return join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result); } /** @@ -110,8 +176,10 @@ public final class StringUtils { * This method will always keep the first occurrence of all strings at their position * in the array, removing the subsequent ones. */ - public static void removeDupes(final ArrayList<String> suggestions) { - if (suggestions.size() < 2) return; + public static void removeDupes(@Nonnull final ArrayList<String> suggestions) { + if (suggestions.size() < 2) { + return; + } int i = 1; // Don't cache suggestions.size(), since we may be removing items while (i < suggestions.size()) { @@ -119,7 +187,7 @@ public final class StringUtils { // Compare each suggestion with each previous suggestion for (int j = 0; j < i; j++) { final String previous = suggestions.get(j); - if (TextUtils.equals(cur, previous)) { + if (equals(cur, previous)) { suggestions.remove(i); i--; break; @@ -129,20 +197,24 @@ public final class StringUtils { } } - public static String capitalizeFirstCodePoint(final String s, final Locale locale) { + @Nonnull + public static String capitalizeFirstCodePoint(@Nonnull final String s, + @Nonnull final Locale locale) { if (s.length() <= 1) { - return toUpperCaseOfStringForLocale(s, true /* needsToUpperCase */, locale); + return s.toUpperCase(getLocaleUsedForToTitleCase(locale)); } // Please refer to the comment below in // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings final int cutoff = s.offsetByCodePoints(0, 1); - return toUpperCaseOfStringForLocale( - s.substring(0, cutoff), true /* needsToUpperCase */, locale) + s.substring(cutoff); + return s.substring(0, cutoff).toUpperCase(getLocaleUsedForToTitleCase(locale)) + + s.substring(cutoff); } - public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) { + @Nonnull + public static String capitalizeFirstAndDowncaseRest(@Nonnull final String s, + @Nonnull final Locale locale) { if (s.length() <= 1) { - return toUpperCaseOfStringForLocale(s, true /* needsToUpperCase */, locale); + return s.toUpperCase(getLocaleUsedForToTitleCase(locale)); } // TODO: fix the bugs below // - It does not work for Serbian, because it fails to account for the "lj" character, @@ -152,17 +224,18 @@ public final class StringUtils { // be capitalized as "IJ" as if they were a single letter in most words (not all). If the // unicode char for the ligature is used however, it works. final int cutoff = s.offsetByCodePoints(0, 1); - final String titleCaseFirstLetter = toUpperCaseOfStringForLocale( - s.substring(0, cutoff), true /* needsToUpperCase */, locale); - return titleCaseFirstLetter + s.substring(cutoff).toLowerCase(locale); + return s.substring(0, cutoff).toUpperCase(getLocaleUsedForToTitleCase(locale)) + + s.substring(cutoff).toLowerCase(locale); } - private static final int[] EMPTY_CODEPOINTS = {}; - - public static int[] toCodePointArray(final CharSequence charSequence) { + @Nonnull + public static int[] toCodePointArray(@Nonnull final CharSequence charSequence) { return toCodePointArray(charSequence, 0, charSequence.length()); } + @Nonnull + private static final int[] EMPTY_CODEPOINTS = {}; + /** * Converts a range of a string to an array of code points. * @param charSequence the source string. @@ -170,7 +243,8 @@ public final class StringUtils { * @param endIndex the end index inside the string in java chars, exclusive. * @return a new array of code points. At most endIndex - startIndex, but possibly less. */ - public static int[] toCodePointArray(final CharSequence charSequence, + @Nonnull + public static int[] toCodePointArray(@Nonnull final CharSequence charSequence, final int startIndex, final int endIndex) { final int length = charSequence.length(); if (length <= 0) { @@ -201,8 +275,8 @@ public final class StringUtils { * @param downCase if this is true, code points will be downcased before being copied. * @return the number of copied code points. */ - public static int copyCodePointsAndReturnCodePointCount(final int[] destination, - final CharSequence charSequence, final int startIndex, final int endIndex, + public static int copyCodePointsAndReturnCodePointCount(@Nonnull final int[] destination, + @Nonnull final CharSequence charSequence, final int startIndex, final int endIndex, final boolean downCase) { int destIndex = 0; for (int index = startIndex; index < endIndex; @@ -216,7 +290,8 @@ public final class StringUtils { return destIndex; } - public static int[] toSortedCodePointArray(final String string) { + @Nonnull + public static int[] toSortedCodePointArray(@Nonnull final String string) { final int[] codePoints = toCodePointArray(string); Arrays.sort(codePoints); return codePoints; @@ -229,7 +304,9 @@ public final class StringUtils { * shorter than the array length. * @return a string constructed from the code point array. */ - public static String getStringFromNullTerminatedCodePointArray(final int[] codePoints) { + @Nonnull + public static String getStringFromNullTerminatedCodePointArray( + @Nonnull final int[] codePoints) { int stringLength = codePoints.length; for (int i = 0; i < codePoints.length; i++) { if (codePoints[i] == 0) { @@ -241,7 +318,7 @@ public final class StringUtils { } // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE. - public static int getCapitalizationType(final String text) { + public static int getCapitalizationType(@Nonnull final String text) { // If the first char is not uppercase, then the word is either all lower case or // camel case, and in either case we return CAPITALIZE_NONE. final int len = text.length(); @@ -277,7 +354,7 @@ public final class StringUtils { return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); } - public static boolean isIdenticalAfterUpcase(final String text) { + public static boolean isIdenticalAfterUpcase(@Nonnull final String text) { final int length = text.length(); int i = 0; while (i < length) { @@ -290,7 +367,7 @@ public final class StringUtils { return true; } - public static boolean isIdenticalAfterDowncase(final String text) { + public static boolean isIdenticalAfterDowncase(@Nonnull final String text) { final int length = text.length(); int i = 0; while (i < length) { @@ -303,8 +380,8 @@ public final class StringUtils { return true; } - public static boolean isIdenticalAfterCapitalizeEachWord(final String text, - final int[] sortedSeparators) { + public static boolean isIdenticalAfterCapitalizeEachWord(@Nonnull final String text, + @Nonnull final int[] sortedSeparators) { boolean needsCapsNext = true; final int len = text.length(); for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { @@ -323,8 +400,9 @@ public final class StringUtils { // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph // which should be capitalized together in *some* cases. - public static String capitalizeEachWord(final String text, final int[] sortedSeparators, - final Locale locale) { + @Nonnull + public static String capitalizeEachWord(@Nonnull final String text, + @Nonnull final int[] sortedSeparators, @Nonnull final Locale locale) { final StringBuilder builder = new StringBuilder(); boolean needsCapsNext = true; final int len = text.length(); @@ -358,9 +436,11 @@ public final class StringUtils { * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the * code complexity, but ideally it should not. It's acceptable for now. */ - public static boolean lastPartLooksLikeURL(final CharSequence text) { + public static boolean lastPartLooksLikeURL(@Nonnull final CharSequence text) { int i = text.length(); - if (0 == i) return false; + if (0 == i) { + return false; + } int wCount = 0; int slashCount = 0; boolean hasSlash = false; @@ -397,11 +477,17 @@ public final class StringUtils { } // End of the text run. // If it starts with www and includes a period, then it looks like a URL. - if (wCount >= 3 && hasPeriod) return true; + if (wCount >= 3 && hasPeriod) { + return true; + } // If it starts with a slash, and the code point before is whitespace, it looks like an URL. - if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true; + if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) { + return true; + } // If it has both a period and a slash, it looks like an URL. - if (hasPeriod && hasSlash) return true; + if (hasPeriod && hasSlash) { + return true; + } // Otherwise, it doesn't look like an URL. return false; } @@ -422,18 +508,24 @@ public final class StringUtils { * @param text the text to examine. * @return whether we're inside a double quote. */ - public static boolean isInsideDoubleQuoteOrAfterDigit(final CharSequence text) { + public static boolean isInsideDoubleQuoteOrAfterDigit(@Nonnull final CharSequence text) { int i = text.length(); - if (0 == i) return false; + if (0 == i) { + return false; + } int codePoint = Character.codePointBefore(text, i); - if (Character.isDigit(codePoint)) return true; + if (Character.isDigit(codePoint)) { + return true; + } int prevCodePoint = 0; while (i > 0) { codePoint = Character.codePointBefore(text, i); if (Constants.CODE_DOUBLE_QUOTE == codePoint) { // If we see a double quote followed by whitespace, then that // was a closing quote. - if (Character.isWhitespace(prevCodePoint)) return false; + if (Character.isWhitespace(prevCodePoint)) { + return false; + } } if (Character.isWhitespace(codePoint) && Constants.CODE_DOUBLE_QUOTE == prevCodePoint) { // If we see a double quote preceded by whitespace, then that @@ -448,7 +540,7 @@ public final class StringUtils { return Constants.CODE_DOUBLE_QUOTE == codePoint; } - public static boolean isEmptyStringOrWhiteSpaces(final String s) { + public static boolean isEmptyStringOrWhiteSpaces(@Nonnull final String s) { final int N = codePointCount(s); for (int i = 0; i < N; ++i) { if (!Character.isWhitespace(s.codePointAt(i))) { @@ -459,12 +551,13 @@ public final class StringUtils { } @UsedForTesting - public static String byteArrayToHexString(final byte[] bytes) { + @Nonnull + public static String byteArrayToHexString(@Nullable final byte[] bytes) { if (bytes == null || bytes.length == 0) { return EMPTY_STRING; } final StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { + for (final byte b : bytes) { sb.append(String.format("%02x", b & 0xff)); } return sb.toString(); @@ -474,8 +567,9 @@ public final class StringUtils { * Convert hex string to byte array. The string length must be an even number. */ @UsedForTesting - public static byte[] hexStringToByteArray(final String hexString) { - if (TextUtils.isEmpty(hexString)) { + @Nullable + public static byte[] hexStringToByteArray(@Nullable final String hexString) { + if (isEmpty(hexString)) { return null; } final int N = hexString.length(); @@ -493,7 +587,8 @@ public final class StringUtils { private static final String LANGUAGE_GREEK = "el"; - private static Locale getLocaleUsedForToTitleCase(final Locale locale) { + @Nonnull + private static Locale getLocaleUsedForToTitleCase(@Nonnull final Locale locale) { // In Greek locale {@link String#toUpperCase(Locale)} eliminates accents from its result. // In order to get accented upper case letter, {@link Locale#ROOT} should be used. if (LANGUAGE_GREEK.equals(locale.getLanguage())) { @@ -502,25 +597,26 @@ public final class StringUtils { return locale; } - public static String toUpperCaseOfStringForLocale(final String text, - final boolean needsToUpperCase, final Locale locale) { - if (text == null || !needsToUpperCase) { - return text; + @Nullable + public static String toTitleCaseOfKeyLabel(@Nullable final String label, + @Nonnull final Locale locale) { + if (label == null) { + return label; } - return text.toUpperCase(getLocaleUsedForToTitleCase(locale)); + return label.toUpperCase(getLocaleUsedForToTitleCase(locale)); } - public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase, - final Locale locale) { - if (!Constants.isLetterCode(code) || !needsToUpperCase) return code; - final String text = newSingleCodePointString(code); - final String casedText = toUpperCaseOfStringForLocale( - text, needsToUpperCase, locale); - return codePointCount(casedText) == 1 - ? casedText.codePointAt(0) : CODE_UNSPECIFIED; + public static int toTitleCaseOfKeyCode(final int code, @Nonnull final Locale locale) { + if (!Constants.isLetterCode(code)) { + return code; + } + final String label = newSingleCodePointString(code); + final String titleCaseLabel = toTitleCaseOfKeyLabel(label, locale); + return codePointCount(titleCaseLabel) == 1 + ? titleCaseLabel.codePointAt(0) : Constants.CODE_UNSPECIFIED; } - public static int getTrailingSingleQuotesCount(final CharSequence charSequence) { + public static int getTrailingSingleQuotesCount(@Nonnull final CharSequence charSequence) { final int lastIndex = charSequence.length() - 1; int i = lastIndex; while (i >= 0 && charSequence.charAt(i) == Constants.CODE_SINGLE_QUOTE) { @@ -529,72 +625,36 @@ public final class StringUtils { return lastIndex - i; } - /** - * Splits the given {@code charSequence} with at occurrences of the given {@code regex}. - * <p> - * This is equivalent to - * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)} - * except that the spans are preserved in the result array. - * </p> - * @param input the character sequence to be split. - * @param regex the regex pattern to be used as the separator. - * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty - * segments. Otherwise, trailing empty segments will be removed before being returned. - * @return the array which contains the result. All the spans in the {@param input} is - * preserved. - */ - @UsedForTesting - public static CharSequence[] split(final CharSequence charSequence, final String regex, - final boolean preserveTrailingEmptySegments) { - // A short-cut for non-spanned strings. - if (!(charSequence instanceof Spanned)) { - // -1 means that trailing empty segments will be preserved. - return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0); - } - - // Hereafter, emulate String.split for CharSequence. - final ArrayList<CharSequence> sequences = new ArrayList<>(); - final Matcher matcher = Pattern.compile(regex).matcher(charSequence); - int nextStart = 0; - boolean matched = false; - while (matcher.find()) { - sequences.add(charSequence.subSequence(nextStart, matcher.start())); - nextStart = matcher.end(); - matched = true; - } - if (!matched) { - // never matched. preserveTrailingEmptySegments is ignored in this case. - return new CharSequence[] { charSequence }; - } - sequences.add(charSequence.subSequence(nextStart, charSequence.length())); - if (!preserveTrailingEmptySegments) { - for (int i = sequences.size() - 1; i >= 0; --i) { - if (!TextUtils.isEmpty(sequences.get(i))) { - break; - } - sequences.remove(i); - } - } - return sequences.toArray(new CharSequence[sequences.size()]); - } - @UsedForTesting public static class Stringizer<E> { - public String stringize(final E element) { - return element != null ? element.toString() : "null"; + @Nonnull + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + @UsedForTesting + @Nonnull + public String stringize(@Nullable final E element) { + if (element == null) { + return "null"; + } + return element.toString(); } @UsedForTesting - public final String join(final E[] array) { + @Nonnull + public final String join(@Nullable final E[] array) { return joinStringArray(toStringArray(array), null /* delimiter */); } @UsedForTesting - public final String join(final E[] array, final String delimiter) { + public final String join(@Nullable final E[] array, @Nullable final String delimiter) { return joinStringArray(toStringArray(array), delimiter); } - protected String[] toStringArray(final E[] array) { + @Nonnull + protected String[] toStringArray(@Nullable final E[] array) { + if (array == null) { + return EMPTY_STRING_ARRAY; + } final String[] stringArray = new String[array.length]; for (int index = 0; index < array.length; index++) { stringArray[index] = stringize(array[index]); @@ -602,10 +662,9 @@ public final class StringUtils { return stringArray; } - protected String joinStringArray(final String[] stringArray, final String delimiter) { - if (stringArray == null) { - return "null"; - } + @Nonnull + protected String joinStringArray(@Nonnull final String[] stringArray, + @Nullable final String delimiter) { if (delimiter == null) { return Arrays.toString(stringArray); } @@ -623,9 +682,8 @@ public final class StringUtils { * @param text the text to be examined. * @return {@code true} if the last composed word contains line-breaking separator. */ - @UsedForTesting - public static boolean hasLineBreakCharacter(final String text) { - if (TextUtils.isEmpty(text)) { + public static boolean hasLineBreakCharacter(@Nullable final String text) { + if (isEmpty(text)) { return false; } for (int i = text.length() - 1; i >= 0; --i) { diff --git a/common/src/com/android/inputmethod/latin/common/UnicodeSurrogate.java b/common/src/com/android/inputmethod/latin/common/UnicodeSurrogate.java new file mode 100644 index 000000000..10974634d --- /dev/null +++ b/common/src/com/android/inputmethod/latin/common/UnicodeSurrogate.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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; + +/** + * Emojis are supplementary characters expressed as a low+high pair. For instance, + * the emoji U+1F625 is encoded as "\uD83D\uDE25" in UTF-16, where '\uD83D' is in + * the range of [0xd800, 0xdbff] and '\uDE25' is in the range of [0xdc00, 0xdfff]. + * {@see http://docs.oracle.com/javase/6/docs/api/java/lang/Character.html#unicode} + */ +public final class UnicodeSurrogate { + private static final char LOW_SURROGATE_MIN = '\uD800'; + private static final char LOW_SURROGATE_MAX = '\uDBFF'; + private static final char HIGH_SURROGATE_MIN = '\uDC00'; + private static final char HIGH_SURROGATE_MAX = '\uDFFF'; + + public static boolean isLowSurrogate(final char c) { + return c >= LOW_SURROGATE_MIN && c <= LOW_SURROGATE_MAX; + } + + public static boolean isHighSurrogate(final char c) { + return c >= HIGH_SURROGATE_MIN && c <= HIGH_SURROGATE_MAX; + } +} |