diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
17 files changed, 320 insertions, 128 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 43a44221d..5e36d9703 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -89,6 +89,8 @@ public final class BinaryDictionary extends Dictionary { private final long mDictSize; private final String mDictFilePath; private final boolean mIsUpdatable; + private boolean mHasUpdated; + private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH]; private final int[] mOutputSuggestionCount = new int[1]; private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS]; @@ -138,6 +140,7 @@ public final class BinaryDictionary extends Dictionary { mDictSize = length; mDictFilePath = filename; mIsUpdatable = isUpdatable; + mHasUpdated = false; mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance); loadDictionary(filename, offset, length, isUpdatable); } @@ -185,6 +188,7 @@ public final class BinaryDictionary extends Dictionary { // TODO: Move native dict into session private final void loadDictionary(final String path, final long startOffset, final long length, final boolean isUpdatable) { + mHasUpdated = false; mNativeDict = openNative(path, startOffset, length, isUpdatable); } @@ -243,24 +247,27 @@ public final class BinaryDictionary extends Dictionary { final String prevWord, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final int sessionId, final float[] inOutLanguageWeight) { - if (!isValidDictionary()) return null; + if (!isValidDictionary()) { + return null; + } Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE); // TODO: toLowerCase in the native code final int[] prevWordCodePointArray = (null == prevWord) ? null : StringUtils.toCodePointArray(prevWord); - final int composerSize = composer.sizeWithoutTrailingSingleQuotes(); - + final InputPointers inputPointers = composer.getInputPointers(); final boolean isGesture = composer.isBatchMode(); - if (composerSize <= 1 || !isGesture) { - if (composerSize > MAX_WORD_LENGTH - 1) return null; - for (int i = 0; i < composerSize; i++) { - mInputCodePoints[i] = composer.getCodeAt(i); + final int inputSize; + if (!isGesture) { + inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount( + mInputCodePoints, MAX_WORD_LENGTH); + if (inputSize < 0) { + return null; } + } else { + inputSize = inputPointers.getPointerSize(); } - final InputPointers ips = composer.getInputPointers(); - final int inputSize = isGesture ? ips.getPointerSize() : composerSize; mNativeSuggestOptions.setIsGesture(isGesture); mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions); if (inOutLanguageWeight != null) { @@ -270,12 +277,12 @@ public final class BinaryDictionary extends Dictionary { } // proximityInfo and/or prevWordForBigrams may not be null. getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(), - getTraverseSession(sessionId).getSession(), ips.getXCoordinates(), - ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints, - inputSize, mNativeSuggestOptions.getOptions(), - prevWordCodePointArray, mOutputSuggestionCount, mOutputCodePoints, mOutputScores, - mSpaceIndices, mOutputTypes, mOutputAutoCommitFirstWordConfidence, - mInputOutputLanguageWeight); + getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(), + inputPointers.getYCoordinates(), inputPointers.getTimes(), + inputPointers.getPointerIds(), mInputCodePoints, inputSize, + mNativeSuggestOptions.getOptions(), prevWordCodePointArray, mOutputSuggestionCount, + mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes, + mOutputAutoCommitFirstWordConfidence, mInputOutputLanguageWeight); if (inOutLanguageWeight != null) { inOutLanguageWeight[0] = mInputOutputLanguageWeight[0]; } @@ -401,6 +408,7 @@ public final class BinaryDictionary extends Dictionary { StringUtils.toCodePointArray(shortcutTarget) : null; addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints, shortcutProbability, isNotAWord, isBlacklisted, timestamp); + mHasUpdated = true; } // Add a bigram entry to binary dictionary with timestamp in native code. @@ -412,6 +420,7 @@ public final class BinaryDictionary extends Dictionary { final int[] codePoints0 = StringUtils.toCodePointArray(word0); final int[] codePoints1 = StringUtils.toCodePointArray(word1); addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp); + mHasUpdated = true; } // Remove a bigram entry form binary dictionary in native code. @@ -422,6 +431,7 @@ public final class BinaryDictionary extends Dictionary { final int[] codePoints0 = StringUtils.toCodePointArray(word0); final int[] codePoints1 = StringUtils.toCodePointArray(word1); removeBigramWordsNative(mNativeDict, codePoints0, codePoints1); + mHasUpdated = true; } public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) { @@ -433,6 +443,7 @@ public final class BinaryDictionary extends Dictionary { } processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict, languageModelParams, processedParamCount); + mHasUpdated = true; if (processedParamCount <= 0) { return; } @@ -451,8 +462,10 @@ public final class BinaryDictionary extends Dictionary { public void flush() { if (!isValidDictionary()) return; - flushNative(mNativeDict, mDictFilePath); - reopen(); + if (mHasUpdated) { + flushNative(mNativeDict, mDictFilePath); + reopen(); + } } public void flushWithGC() { diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index 4e17f8389..d5873d70f 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -100,10 +100,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { }); } - public void reopen(final Context context) { - registerObserver(context); - } - @Override public synchronized void close() { if (mObserver != null) { diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index aea4811a9..64e9d2b51 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -262,6 +262,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { + if (mBinaryDictionary == null) { + return; + } runGCAfterAllPrioritizedTasksIfRequiredLocked(mindsBlockByGC); } }); @@ -274,9 +277,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } private void runGCAfterAllPrioritizedTasksIfRequiredLocked(final boolean mindsBlockByGC) { - if (mBinaryDictionary == null) { - return; - } // needsToRunGC() have to be called with lock. if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) { if (setProcessingLargeTaskIfNot()) { @@ -301,9 +301,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected void addWordDynamically(final String word, final int frequency, final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord, final boolean isBlacklisted, final int timestamp) { + reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { + if (mBinaryDictionary == null) { + return; + } runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq, isNotAWord, isBlacklisted, timestamp); @@ -323,9 +327,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ protected void addBigramDynamically(final String word0, final String word1, final int frequency, final int timestamp) { + reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { + if (mBinaryDictionary == null) { + return; + } runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); addBigramDynamicallyLocked(word0, word1, frequency, timestamp); } @@ -341,9 +349,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * Dynamically remove a word bigram in the dictionary. */ protected void removeBigramDynamically(final String word0, final String word1) { + reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { + if (mBinaryDictionary == null) { + return; + } runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); mBinaryDictionary.removeBigramWords(word0, word1); } @@ -360,14 +372,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected void addMultipleDictionaryEntriesDynamically( final ArrayList<LanguageModelParam> languageModelParams, final AddMultipleDictionaryEntriesCallback callback) { + reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { + if (mBinaryDictionary == null) { + return; + } final boolean locked = setProcessingLargeTaskIfNot(); try { - if (mBinaryDictionary == null) { - return; - } mBinaryDictionary.addMultipleDictionaryEntries( languageModelParams.toArray( new LanguageModelParam[languageModelParams.size()])); diff --git a/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java b/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java index 9870faa98..be54b669b 100644 --- a/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java +++ b/java/src/com/android/inputmethod/latin/ImportantNoticeDialog.java @@ -23,6 +23,7 @@ import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; import android.content.DialogInterface.OnShowListener; +import com.android.inputmethod.latin.utils.DialogUtils; import com.android.inputmethod.latin.utils.ImportantNoticeUtils; /** @@ -40,7 +41,7 @@ public final class ImportantNoticeDialog extends AlertDialog implements OnShowLi public ImportantNoticeDialog( final Context context, final ImportantNoticeDialogListener listener) { - super(context, THEME_HOLO_DARK); + super(DialogUtils.getPlatformDialogThemeContext(context)); mListener = listener; mNextImportantNoticeVersion = ImportantNoticeUtils.getNextImportantNoticeVersion(context); setMessage(ImportantNoticeUtils.getNextImportantNoticeContents(context)); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 7ba7cc82a..0594c68cc 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -83,10 +83,12 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; 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.DialogUtils; import com.android.inputmethod.latin.utils.ImportantNoticeUtils; import com.android.inputmethod.latin.utils.IntentUtils; import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; +import com.android.inputmethod.latin.utils.StatsUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import com.android.inputmethod.research.ResearchLogger; @@ -509,6 +511,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter); DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this); + + StatsUtils.onCreateCompleted(this); } // Has to be package-visible for unit tests @@ -628,9 +632,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen PersonalizationDictionarySessionRegistrar.close(this); LatinImeLogger.commit(); LatinImeLogger.onDestroy(); + StatsUtils.onDestroy(); super.onDestroy(); } + @UsedForTesting + public void recycle() { + mInputLogic.recycle(); + } + @Override public void onConfigurationChanged(final Configuration conf) { // If orientation changed while predicting, commit the change @@ -1667,8 +1677,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } }; - final AlertDialog.Builder builder = - new AlertDialog.Builder(this).setItems(items, listener).setTitle(title); + final AlertDialog.Builder builder = new AlertDialog.Builder( + DialogUtils.getPlatformDialogThemeContext(this)); + builder.setItems(items, listener).setTitle(title); showOptionDialog(builder.create()); } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index f31fb134c..02f18cdd3 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -131,29 +131,42 @@ public final class WordComposer { return mCodePointSize; } - public boolean isSingleLetter() { - return size() == 1; + /** + * 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. + * @param maxSize the size of the array. + * @return the number of copied code points. + */ + public int copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount( + final int[] destination, final int maxSize) { + int i = mTypedWordCache.length() - 1; + while (i >= 0 && mTypedWordCache.charAt(i) == Constants.CODE_SINGLE_QUOTE) { + --i; + } + if (i < 0) { + // The string is empty or contains only single quotes. + return 0; + } + final int codePointSize = Character.codePointCount(mTypedWordCache, 0, i); + if (codePointSize > maxSize) { + return -1; + } + return StringUtils.copyCodePointsAndReturnCodePointCount(destination, mTypedWordCache, 0, + i + 1, true /* downCase */); } - // When the composition contains trailing quotes, we don't pass them to the suggestion engine. - // This is because "'tgis'" should be corrected to "'this'", but we can't afford to consider - // single quotes as separators because of their very common use as apostrophes. - public int sizeWithoutTrailingSingleQuotes() { - return size() - mTrailingSingleQuotesCount; + public boolean isSingleLetter() { + return size() == 1; } public final boolean isComposingWord() { return size() > 0; } - // TODO: make sure that the index should not exceed MAX_WORD_LENGTH - public int getCodeAt(int index) { - if (index >= MAX_WORD_LENGTH) { - return -1; - } - return mPrimaryKeyCodes[index]; - } - public InputPointers getInputPointers() { return mInputPointers; } @@ -165,67 +178,61 @@ public final class WordComposer { } /** - * Add a new event for a key stroke, with the pressed key's code point with the touch point - * coordinates. + * Process an input event. + * + * All input events should be supported, including software/hardware events, characters as well + * as deletions, multiple inputs and gestures. + * + * @param event the event to process. */ - public void add(final Event event) { + public void processEvent(final Event event) { final int primaryCode = event.mCodePoint; final int keyX = event.mX; final int keyY = event.mY; final int newIndex = size(); - processEvent(event); - mCursorPositionWithinWord = mCodePointSize; - if (newIndex < MAX_WORD_LENGTH) { - mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE - ? Character.toLowerCase(primaryCode) : primaryCode; - // In the batch input mode, the {@code mInputPointers} holds batch input points and - // shouldn't be overridden by the "typed key" coordinates - // (See {@link #setBatchInputWord}). - if (!mIsBatchMode) { - // TODO: Set correct pointer id and time - mInputPointers.addPointerAt(newIndex, keyX, keyY, 0, 0); - } - } - mIsFirstCharCapitalized = isFirstCharCapitalized( - newIndex, primaryCode, mIsFirstCharCapitalized); - if (Character.isUpperCase(primaryCode)) mCapsCount++; - if (Character.isDigit(primaryCode)) mDigitsCount++; - if (Constants.CODE_SINGLE_QUOTE == primaryCode) { - ++mTrailingSingleQuotesCount; - } else { - mTrailingSingleQuotesCount = 0; - } - mAutoCorrection = null; - } - - private void processEvent(final Event event) { mCombinerChain.processEvent(mEvents, event); mEvents.add(event); refreshTypedWordCache(); - } - - /** - * Delete the last composing unit as a result of hitting backspace. - */ - public void deleteLast(final Event event) { - processEvent(event); + mCursorPositionWithinWord = mCodePointSize; // We may have deleted the last one. - if (0 == size()) { + if (0 == mCodePointSize) { mIsFirstCharCapitalized = false; } - if (mTrailingSingleQuotesCount > 0) { - --mTrailingSingleQuotesCount; - } else { - int i = mTypedWordCache.length(); - while (i > 0) { - i = Character.offsetByCodePoints(mTypedWordCache, i, -1); - if (Constants.CODE_SINGLE_QUOTE != Character.codePointAt(mTypedWordCache, i)) { - break; + if (Constants.CODE_DELETE == event.mKeyCode) { + if (mTrailingSingleQuotesCount > 0) { + --mTrailingSingleQuotesCount; + } else { + // Delete, but we didn't end in a quote: must recompute mTrailingSingleQuotesCount + // We're only searching for single quotes, so no need to account for code points + for (int i = mTypedWordCache.length() - 1; i > 0; --i) { + if (Constants.CODE_SINGLE_QUOTE != mTypedWordCache.charAt(i)) { + break; + } + ++mTrailingSingleQuotesCount; } + } + } else { + if (Constants.CODE_SINGLE_QUOTE == primaryCode) { ++mTrailingSingleQuotesCount; + } else { + mTrailingSingleQuotesCount = 0; + } + if (newIndex < MAX_WORD_LENGTH) { + mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE + ? Character.toLowerCase(primaryCode) : primaryCode; + // In the batch input mode, the {@code mInputPointers} holds batch input points and + // shouldn't be overridden by the "typed key" coordinates + // (See {@link #setBatchInputWord}). + if (!mIsBatchMode) { + // TODO: Set correct pointer id and time + mInputPointers.addPointerAt(newIndex, keyX, keyY, 0, 0); + } } + mIsFirstCharCapitalized = isFirstCharCapitalized( + newIndex, primaryCode, mIsFirstCharCapitalized); + if (Character.isUpperCase(primaryCode)) mCapsCount++; + if (Character.isDigit(primaryCode)) mDigitsCount++; } - mCursorPositionWithinWord = mCodePointSize; mAutoCorrection = null; } @@ -300,7 +307,7 @@ public final class WordComposer { final int codePoint = Character.codePointAt(word, i); // We don't want to override the batch input points that are held in mInputPointers // (See {@link #add(int,int,int)}). - add(Event.createEventForCodePointFromUnknownSource(codePoint)); + processEvent(Event.createEventForCodePointFromUnknownSource(codePoint)); } } @@ -317,7 +324,7 @@ public final class WordComposer { reset(); final int length = codePoints.length; for (int i = 0; i < length; ++i) { - add(Event.createEventForCodePointFromAlreadyTypedText(codePoints[i], + processEvent(Event.createEventForCodePointFromAlreadyTypedText(codePoints[i], CoordinateUtils.xFromArray(coordinates, i), CoordinateUtils.yFromArray(coordinates, i))); } diff --git a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java index 800f56597..139e73aa4 100644 --- a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java +++ b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java @@ -28,6 +28,7 @@ import com.android.inputmethod.latin.BinaryDictionaryGetter; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.DialogUtils; import com.android.inputmethod.latin.utils.DictionaryInfoUtils; import com.android.inputmethod.latin.utils.LocaleUtils; @@ -70,7 +71,7 @@ public class ExternalDictionaryGetterForDebug { } private static void showNoFileDialog(final Context context) { - new AlertDialog.Builder(context) + new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context)) .setMessage(R.string.read_external_dictionary_no_files_message) .setPositiveButton(android.R.string.ok, new OnClickListener() { @Override @@ -81,8 +82,8 @@ public class ExternalDictionaryGetterForDebug { } private static void showChooseFileDialog(final Context context, final String[] fileNames) { - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.read_external_dictionary_multiple_files_title) + new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context)) + .setTitle(R.string.read_external_dictionary_multiple_files_title) .setItems(fileNames, new OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { @@ -111,7 +112,7 @@ public class ExternalDictionaryGetterForDebug { final String title = String.format( context.getString(R.string.read_external_dictionary_confirm_install_message), languageName); - new AlertDialog.Builder(context) + new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context)) .setTitle(title) .setMessage(message) .setNegativeButton(android.R.string.cancel, new OnClickListener() { @@ -167,7 +168,7 @@ public class ExternalDictionaryGetterForDebug { } } catch (IOException e) { // There was an error: show a dialog - new AlertDialog.Builder(context) + new AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(context)) .setTitle(R.string.error) .setMessage(e.toString()) .setPositiveButton(android.R.string.ok, new OnClickListener() { diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java index e6fa1cdad..af899c040 100644 --- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java +++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java @@ -30,6 +30,12 @@ public final class ProductionFlag { public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false; + // When true, enable {@link InputMethodService#onUpdateCursor} callback with + // {@link InputMethodService#setCursorAnchorMonitorMode}, which is not yet available in + // API level 19. Do not turn this on in production until the new API becomes publicly + // available. + public static final boolean USES_CURSOR_ANCHOR_MONITOR = false; + // Include all suggestions from all dictionaries in {@link SuggestedWords#mRawSuggestions}. public static final boolean INCLUDE_RAW_SUGGESTIONS = false; } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 30357943c..bf8467eb6 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -148,6 +148,17 @@ public final class InputLogic { mInputLogicHandler.reset(); } + // Normally this class just gets out of scope after the process ends, but in unit tests, we + // create several instances of LatinIME in the same process, which results in several + // instances of InputLogic. This cleans up the associated handler so that tests don't leak + // handlers. + public void recycle() { + final InputLogicHandler inputLogicHandler = mInputLogicHandler; + mInputLogicHandler = InputLogicHandler.NULL_HANDLER; + inputLogicHandler.destroy(); + mSuggest.mDictionaryFacilitator.closeDictionaries(); + } + /** * React to a string input. * @@ -729,7 +740,7 @@ public final class InputLogic { resetComposingState(false /* alsoResetLastComposedWord */); } if (isComposingWord) { - mWordComposer.add(inputTransaction.mEvent); + mWordComposer.processEvent(inputTransaction.mEvent); // If it's the first letter, make note of auto-caps state if (mWordComposer.isSingleLetter()) { // We pass 1 to getPreviousWordForSuggestion because we were not composing a word @@ -825,13 +836,11 @@ public final class InputLogic { } if (Constants.CODE_SPACE == codePoint) { - if (inputTransaction.mSettingsValues.isSuggestionsRequested()) { - if (maybeDoubleSpacePeriod(inputTransaction)) { - inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - mSpaceState = SpaceState.DOUBLE; - } else if (!mSuggestedWords.isPunctuationSuggestions()) { - mSpaceState = SpaceState.WEAK; - } + if (maybeDoubleSpacePeriod(inputTransaction)) { + inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); + mSpaceState = SpaceState.DOUBLE; + } else if (!mSuggestedWords.isPunctuationSuggestions()) { + mSpaceState = SpaceState.WEAK; } startDoubleSpacePeriodCountdown(inputTransaction); @@ -897,7 +906,7 @@ public final class InputLogic { mWordComposer.reset(); mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion); } else { - mWordComposer.deleteLast(inputTransaction.mEvent); + mWordComposer.processEvent(inputTransaction.mEvent); } mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); inputTransaction.setRequiresUpdateSuggestions(); diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java index e3b8ab465..64bba681f 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; +import com.android.inputmethod.compat.LooperCompatUtils; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.Suggest; @@ -80,6 +81,12 @@ class InputLogicHandler implements Handler.Callback { mNonUIThreadHandler.removeCallbacksAndMessages(null); } + // In unit tests, we create several instances of LatinIME, which results in several instances + // of InputLogicHandler. To avoid these handlers lingering, we call this. + public void destroy() { + LooperCompatUtils.quitSafely(mNonUIThreadHandler.getLooper()); + } + /** * Handle a message. * @see android.os.Handler.Callback#handleMessage(android.os.Message) diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java index 6f84e1f10..712e314a8 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java +++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java @@ -66,7 +66,7 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB } // Flush pending writes. flush(); - // TODO: Quit depending on finalize() and really close the dictionary file. + super.close(); } public void flush() { diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java index 6dae6206c..39977e76f 100644 --- a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java +++ b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java @@ -48,6 +48,7 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.DialogUtils; import com.android.inputmethod.latin.utils.IntentUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; @@ -517,7 +518,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { private AlertDialog createDialog( @SuppressWarnings("unused") final SubtypePreference subtypePref) { - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final AlertDialog.Builder builder = new AlertDialog.Builder( + DialogUtils.getPlatformDialogThemeContext(getActivity())); builder.setTitle(R.string.custom_input_styles_title) .setMessage(R.string.custom_input_style_note_message) .setNegativeButton(R.string.not_now, null) diff --git a/java/src/com/android/inputmethod/latin/utils/DialogUtils.java b/java/src/com/android/inputmethod/latin/utils/DialogUtils.java new file mode 100644 index 000000000..a05c932d0 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/DialogUtils.java @@ -0,0 +1,34 @@ +/* + * 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.utils; + +import android.content.Context; +import android.view.ContextThemeWrapper; + +import com.android.inputmethod.latin.R; + +public final class DialogUtils { + private DialogUtils() { + // This utility class is not publicly instantiable. + } + + public static Context getPlatformDialogThemeContext(final Context context) { + // Because {@link AlertDialog.Builder.create()} doesn't honor the specified theme with + // createThemeContextWrapper=false, the result dialog box has unneeded paddings around it. + return new ContextThemeWrapper(context, R.style.platformDialogTheme); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java index 89837c641..1ca895fdb 100644 --- a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java @@ -18,8 +18,6 @@ package com.android.inputmethod.latin.utils; import android.view.inputmethod.InputMethodSubtype; -import java.util.Locale; - public final class SpacebarLanguageUtils { private SpacebarLanguageUtils() { // Intentional empty constructor for utility class. @@ -55,7 +53,6 @@ public final class SpacebarLanguageUtils { if (SubtypeLocaleUtils.isNoLanguage(subtype)) { return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype); } - final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype); - return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(locale.getLanguage()); + return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(subtype.getLocale()); } } diff --git a/java/src/com/android/inputmethod/latin/utils/StatsUtils.java b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java new file mode 100644 index 000000000..a059f877b --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java @@ -0,0 +1,53 @@ +/* + * 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.utils; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; + +import com.android.inputmethod.latin.settings.Settings; + +public final class StatsUtils { + private static final String TAG = StatsUtils.class.getSimpleName(); + private static final StatsUtils sInstance = new StatsUtils(); + + public static void onCreateCompleted(final Context context) { + sInstance.onCreateCompletedInternal(context); + } + + private void onCreateCompletedInternal(final Context context) { + mContext = context; + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); + final Boolean usePersonalizedDict = + prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true); + Log.d(TAG, "onCreateCompleted. context: " + context.toString() + "usePersonalizedDict: " + + usePersonalizedDict); + } + + public static void onDestroy() { + sInstance.onDestroyInternal(); + } + + private void onDestroyInternal() { + Log.d(TAG, "onDestroy. context: " + mContext.toString()); + mContext = null; + } + + private Context mContext; +} diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java index accbc8b7b..374badc19 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -191,13 +191,42 @@ public final class StringUtils { } final int[] codePoints = new int[Character.codePointCount(charSequence, startIndex, endIndex)]; + copyCodePointsAndReturnCodePointCount(codePoints, charSequence, startIndex, endIndex, + false /* downCase */); + return codePoints; + } + + /** + * Copies the codepoints in a CharSequence to an int array. + * + * This method assumes there is enough space in the array to store the code points. The size + * can be measured with Character#codePointCount(CharSequence, int, int) before passing to this + * method. If the int array is too small, an ArrayIndexOutOfBoundsException will be thrown. + * Also, this method makes no effort to be thread-safe. Do not modify the CharSequence while + * this method is running, or the behavior is undefined. + * This method can optionally downcase code points before copying them, but it pays no attention + * to locale while doing so. + * + * @param destination the int array. + * @param charSequence the CharSequence. + * @param startIndex the start index inside the string in java chars, inclusive. + * @param endIndex the end index inside the string in java chars, exclusive. + * @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, + final boolean downCase) { int destIndex = 0; for (int index = startIndex; index < endIndex; index = Character.offsetByCodePoints(charSequence, index, 1)) { - codePoints[destIndex] = Character.codePointAt(charSequence, index); + final int codePoint = Character.codePointAt(charSequence, index); + // TODO: stop using this, as it's not aware of the locale and does not always do + // the right thing. + destination[destIndex] = downCase ? Character.toLowerCase(codePoint) : codePoint; destIndex++; } - return codePoints; + return destIndex; } public static int[] toSortedCodePointArray(final String string) { diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java index 2452864d5..b37779bdc 100644 --- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java @@ -25,7 +25,7 @@ import android.os.Build; import android.util.Log; import android.view.inputmethod.InputMethodSubtype; -import com.android.inputmethod.latin.DictionaryFactory; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import java.util.Arrays; @@ -33,10 +33,10 @@ import java.util.HashMap; import java.util.Locale; public final class SubtypeLocaleUtils { - static final String TAG = SubtypeLocaleUtils.class.getSimpleName(); - // This class must be located in the same package as LatinIME.java. - private static final String RESOURCE_PACKAGE_NAME = - DictionaryFactory.class.getPackage().getName(); + private static final String TAG = SubtypeLocaleUtils.class.getSimpleName(); + + // This reference class {@link Constants} must be located in the same package as LatinIME.java. + private static final String RESOURCE_PACKAGE_NAME = Constants.class.getPackage().getName(); // Special language code to represent "no language". public static final String NO_LANGUAGE = "zz"; @@ -44,7 +44,8 @@ public final class SubtypeLocaleUtils { public static final String EMOJI = "emoji"; public static final int UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic; - private static boolean sInitialized = false; + 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. @@ -77,9 +78,16 @@ public final class SubtypeLocaleUtils { } // Note that this initialization method can be called multiple times. - public static synchronized void init(final Context context) { - if (sInitialized) return; + public static void init(final Context context) { + synchronized (sInitializeLock) { + if (sInitialized == false) { + initLocked(context); + sInitialized = true; + } + } + } + private static void initLocked(final Context context) { final Resources res = context.getResources(); sResources = res; @@ -122,8 +130,6 @@ public final class SubtypeLocaleUtils { final String keyboardLayoutSet = keyboardLayoutSetMap[i + 1]; sLocaleAndExtraValueToKeyboardLayoutSetMap.put(key, keyboardLayoutSet); } - - sInitialized = true; } public static String[] getPredefinedKeyboardLayoutSet() { @@ -167,8 +173,18 @@ public final class SubtypeLocaleUtils { return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); } + public static String getSubtypeLanguageDisplayName(final String localeString) { + final Locale locale = LocaleUtils.constructLocaleFromString(localeString); + final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); + return getSubtypeLocaleDisplayNameInternal(locale.getLanguage(), displayLocale); + } + private static String getSubtypeLocaleDisplayNameInternal(final String localeString, 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); + } final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString); final String displayName; if (exceptionalNameResId != null) { @@ -179,9 +195,6 @@ public final class SubtypeLocaleUtils { } }; displayName = getExceptionalName.runInLocale(sResources, displayLocale); - } else if (NO_LANGUAGE.equals(localeString)) { - // No language subtype should be displayed in system locale. - return sResources.getString(R.string.subtype_no_language); } else { final Locale locale = LocaleUtils.constructLocaleFromString(localeString); displayName = locale.getDisplayName(displayLocale); |