diff options
7 files changed, 231 insertions, 251 deletions
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index dc2e1af22..4fac5ed2d 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -757,10 +757,11 @@ public final class InputLogic { final int codePoint = inputTransaction.mEvent.mCodePoint; final SettingsValues settingsValues = inputTransaction.mSettingsValues; boolean didAutoCorrect = false; + final boolean wasComposingWord = mWordComposer.isComposingWord(); // We avoid sending spaces in languages without spaces if we were composing. final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint && !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces - && mWordComposer.isComposingWord(); + && wasComposingWord; if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the separator at the current cursor position. @@ -811,13 +812,16 @@ public final class InputLogic { if (Constants.CODE_SPACE == codePoint) { if (maybeDoubleSpacePeriod(inputTransaction)) { inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); + inputTransaction.setRequiresUpdateSuggestions(); mSpaceState = SpaceState.DOUBLE; } else if (!mSuggestedWords.isPunctuationSuggestions()) { mSpaceState = SpaceState.WEAK; } startDoubleSpacePeriodCountdown(inputTransaction); - inputTransaction.setRequiresUpdateSuggestions(); + if (wasComposingWord) { + inputTransaction.setRequiresUpdateSuggestions(); + } } else { if (swapWeakSpace) { swapSwapperAndSpace(inputTransaction); @@ -911,6 +915,11 @@ public final class InputLogic { if (mConnection.revertDoubleSpacePeriod()) { // No need to reset mSpaceState, it has already be done (that's why we // receive it as a parameter) + inputTransaction.setRequiresUpdateSuggestions(); + mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime( + WordComposer.CAPS_MODE_OFF, + getPrevWordsInfoFromNthPreviousWordForSuggestion( + inputTransaction.mSettingsValues.mSpacingAndPunctuations, 1)); return; } } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) { diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java index c08697c4b..61da1b789 100644 --- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java @@ -19,23 +19,42 @@ package com.android.inputmethod.latin.utils; import com.android.inputmethod.annotations.UsedForTesting; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; /** * Utilities to manage executors. */ public class ExecutorUtils { - private static final ConcurrentHashMap<String, PrioritizedSerialExecutor> sExecutorMap = + private static final ConcurrentHashMap<String, ExecutorService> sExecutorMap = new ConcurrentHashMap<>(); + private static class ThreadFactoryWithId implements ThreadFactory { + private final String mId; + + public ThreadFactoryWithId(final String id) { + mId = id; + } + + @Override + public Thread newThread(final Runnable r) { + return new Thread(r, "Executor - " + mId); + } + } + /** * Gets the executor for the given id. */ - public static PrioritizedSerialExecutor getExecutor(final String id) { - PrioritizedSerialExecutor executor = sExecutorMap.get(id); + public static ExecutorService getExecutor(final String id) { + ExecutorService executor = sExecutorMap.get(id); if (executor == null) { synchronized(sExecutorMap) { - executor = new PrioritizedSerialExecutor(id); - sExecutorMap.put(id, executor); + executor = sExecutorMap.get(id); + if (executor == null) { + executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id)); + sExecutorMap.put(id, executor); + } } } return executor; @@ -47,7 +66,7 @@ public class ExecutorUtils { @UsedForTesting public static void shutdownAllExecutors() { synchronized(sExecutorMap) { - for (final PrioritizedSerialExecutor executor : sExecutorMap.values()) { + for (final ExecutorService executor : sExecutorMap.values()) { executor.execute(new Runnable() { @Override public void run() { diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java deleted file mode 100644 index 21949ffbd..000000000 --- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.inputmethod.annotations.UsedForTesting; - -import java.util.Queue; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * An object that executes submitted tasks using a thread. - */ -public class PrioritizedSerialExecutor { - public static final String TAG = PrioritizedSerialExecutor.class.getSimpleName(); - - private final Object mLock = new Object(); - - private final Queue<Runnable> mTasks; - private final Queue<Runnable> mPrioritizedTasks; - private boolean mIsShutdown; - private final ThreadPoolExecutor mThreadPoolExecutor; - - // The task which is running now. - private Runnable mActive; - - private static class ThreadFactoryWithId implements ThreadFactory { - private final String mId; - - public ThreadFactoryWithId(final String id) { - mId = id; - } - - @Override - public Thread newThread(final Runnable r) { - return new Thread(r, TAG + " - " + mId); - } - } - - public PrioritizedSerialExecutor(final String id) { - mTasks = new ConcurrentLinkedQueue<>(); - mPrioritizedTasks = new ConcurrentLinkedQueue<>(); - mIsShutdown = false; - mThreadPoolExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */, - 0 /* keepAliveTime */, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1), - new ThreadFactoryWithId(id)); - } - - /** - * Enqueues the given task into the task queue. - * @param r the enqueued task - */ - public void execute(final Runnable r) { - synchronized(mLock) { - if (!mIsShutdown) { - mTasks.offer(new Runnable() { - @Override - public void run() { - try { - r.run(); - } finally { - scheduleNext(); - } - } - }); - if (mActive == null) { - scheduleNext(); - } - } - } - } - - /** - * Enqueues the given task into the prioritized task queue. - * @param r the enqueued task - */ - @UsedForTesting - public void executePrioritized(final Runnable r) { - synchronized(mLock) { - if (!mIsShutdown) { - mPrioritizedTasks.offer(new Runnable() { - @Override - public void run() { - try { - r.run(); - } finally { - scheduleNext(); - } - } - }); - if (mActive == null) { - scheduleNext(); - } - } - } - } - - private boolean fetchNextTasksLocked() { - mActive = mPrioritizedTasks.poll(); - if (mActive == null) { - mActive = mTasks.poll(); - } - return mActive != null; - } - - private void scheduleNext() { - synchronized(mLock) { - if (fetchNextTasksLocked()) { - mThreadPoolExecutor.execute(mActive); - } - } - } - - public void shutdown() { - synchronized(mLock) { - mIsShutdown = true; - mThreadPoolExecutor.shutdown(); - } - } -} diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index a9444160f..460f600ac 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -481,6 +481,27 @@ public class InputLogicTests extends InputTestsBase { suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); } + public void testPredictionsWithDoubleSpaceToPeriod() { + final String WORD_TO_TYPE = "Barack "; + type(WORD_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // No need to test here, testPredictionsAfterSpace is testing it already + type(" "); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // Test the predictions have been cleared + SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("predictions cleared after double-space-to-period", suggestedWords.size(), 0); + type(Constants.CODE_DELETE); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + // Test the first prediction is displayed + suggestedWords = mLatinIME.getSuggestedWordsForTest(); + assertEquals("predictions after cancel double-space-to-period", "Obama", + suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); + } + public void testPredictionsAfterManualPick() { final String WORD_TO_TYPE = "Barack"; type(WORD_TO_TYPE); diff --git a/tests/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryTests.java new file mode 100644 index 000000000..0f2f9814b --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryTests.java @@ -0,0 +1,117 @@ +/* + * 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.personalization; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.DictionaryFacilitator; +import com.android.inputmethod.latin.ExpandableBinaryDictionary; +import com.android.inputmethod.latin.ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback; +import com.android.inputmethod.latin.makedict.CodePointUtils; +import com.android.inputmethod.latin.settings.SpacingAndPunctuations; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; + +/** + * Unit tests for personalization dictionary + */ +@LargeTest +public class PersonalizationDictionaryTests extends AndroidTestCase { + private static final String TAG = PersonalizationDictionaryTests.class.getSimpleName(); + + private static final Locale LOCALE_EN_US = new Locale("en", "US"); + private static final String DUMMY_PACKAGE_NAME = "test.package.name"; + private static final long TIMEOUT_TO_WAIT_DICTIONARY_OPERATIONS_IN_SECONDS = 120; + + private DictionaryFacilitator getDictionaryFacilitator() { + final ArrayList<String> dictTypes = new ArrayList<>(); + dictTypes.add(Dictionary.TYPE_MAIN); + dictTypes.add(Dictionary.TYPE_PERSONALIZATION); + final DictionaryFacilitator dictionaryFacilitator = new DictionaryFacilitator(); + dictionaryFacilitator.resetDictionariesForTesting(getContext(), LOCALE_EN_US, dictTypes, + new HashMap<String, File>(), new HashMap<String, Map<String, String>>()); + return dictionaryFacilitator; + } + + public void testAddManyTokens() { + final DictionaryFacilitator dictionaryFacilitator = getDictionaryFacilitator(); + dictionaryFacilitator.clearPersonalizationDictionary(); + final int dataChunkCount = 20; + final int wordCountInOneChunk = 2000; + final Random random = new Random(System.currentTimeMillis()); + final int[] codePointSet = CodePointUtils.LATIN_ALPHABETS_LOWER; + + final SpacingAndPunctuations spacingAndPunctuations = + new SpacingAndPunctuations(getContext().getResources()); + + final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds( + System.currentTimeMillis()); + + for (int i = 0; i < dataChunkCount; i++) { + final ArrayList<String> tokens = new ArrayList<>(); + for (int j = 0; j < wordCountInOneChunk; j++) { + tokens.add(CodePointUtils.generateWord(random, codePointSet)); + } + final PersonalizationDataChunk personalizationDataChunk = new PersonalizationDataChunk( + true /* inputByUser */, tokens, timeStampInSeconds, DUMMY_PACKAGE_NAME); + final CountDownLatch countDownLatch = new CountDownLatch(1); + final AddMultipleDictionaryEntriesCallback callback = + new AddMultipleDictionaryEntriesCallback() { + @Override + public void onFinished() { + countDownLatch.countDown(); + } + }; + dictionaryFacilitator.addEntriesToPersonalizationDictionary(personalizationDataChunk, + spacingAndPunctuations, callback); + try { + countDownLatch.await(TIMEOUT_TO_WAIT_DICTIONARY_OPERATIONS_IN_SECONDS, + TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for finishing dictionary operations.", e); + } + } + dictionaryFacilitator.flushPersonalizationDictionary(); + try { + dictionaryFacilitator.waitForLoadingDictionariesForTesting( + TIMEOUT_TO_WAIT_DICTIONARY_OPERATIONS_IN_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for finishing dictionary operations.", e); + } + final String dictName = ExpandableBinaryDictionary.getDictName( + PersonalizationDictionary.NAME, LOCALE_EN_US, null /* dictFile */); + final File dictFile = ExpandableBinaryDictionary.getDictFile( + getContext(), dictName, null /* dictFile */); + + final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), + 0 /* offset */, 0 /* size */, + true /* useFullEditDistance */, LOCALE_EN_US, Dictionary.TYPE_PERSONALIZATION, + true /* isUpdatable */); + assertTrue(binaryDictionary.isValidDictionary()); + } +} diff --git a/tests/src/com/android/inputmethod/latin/utils/ExecutorUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/ExecutorUtilsTests.java new file mode 100644 index 000000000..ae2623d12 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/ExecutorUtilsTests.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013 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.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Unit tests for ExecutorUtils. + */ +@MediumTest +public class ExecutorUtilsTests extends AndroidTestCase { + private static final String TAG = ExecutorUtilsTests.class.getSimpleName(); + + private static final String TEST_EXECUTOR_ID = "test"; + private static final int NUM_OF_TASKS = 10; + private static final int DELAY_FOR_WAITING_TASKS_MILLISECONDS = 500; + + public void testExecute() { + final ExecutorService executor = ExecutorUtils.getExecutor(TEST_EXECUTOR_ID); + final AtomicInteger v = new AtomicInteger(0); + for (int i = 0; i < NUM_OF_TASKS; ++i) { + executor.execute(new Runnable() { + @Override + public void run() { + v.incrementAndGet(); + } + }); + } + try { + executor.awaitTermination(DELAY_FOR_WAITING_TASKS_MILLISECONDS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Log.d(TAG, "Exception while sleeping.", e); + } + + assertEquals(NUM_OF_TASKS, v.get()); + } +} diff --git a/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java b/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java deleted file mode 100644 index 8b78816ce..000000000 --- a/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2013 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.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; -import android.util.Log; - -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Unit tests for PrioritizedSerialExecutor. - * TODO: Add more detailed tests to make use of priorities, etc. - */ -@MediumTest -public class PrioritizedSerialExecutorTests extends AndroidTestCase { - private static final String TAG = PrioritizedSerialExecutorTests.class.getSimpleName(); - - private static final String TEST_EXECUTOR_ID = "test"; - private static final int NUM_OF_TASKS = 10; - private static final int DELAY_FOR_WAITING_TASKS_MILLISECONDS = 500; - - public void testExecute() { - final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor(TEST_EXECUTOR_ID); - final AtomicInteger v = new AtomicInteger(0); - for (int i = 0; i < NUM_OF_TASKS; ++i) { - executor.execute(new Runnable() { - @Override - public void run() { - v.incrementAndGet(); - } - }); - } - try { - Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS); - } catch (InterruptedException e) { - Log.d(TAG, "Exception while sleeping.", e); - } - - assertEquals(NUM_OF_TASKS, v.get()); - } - - public void testExecutePrioritized() { - final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor(TEST_EXECUTOR_ID); - final AtomicInteger v = new AtomicInteger(0); - for (int i = 0; i < NUM_OF_TASKS; ++i) { - executor.executePrioritized(new Runnable() { - @Override - public void run() { - v.incrementAndGet(); - } - }); - } - try { - Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS); - } catch (InterruptedException e) { - Log.d(TAG, "Exception while sleeping.", e); - } - - assertEquals(NUM_OF_TASKS, v.get()); - } - - public void testExecuteCombined() { - final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor(TEST_EXECUTOR_ID); - final AtomicInteger v = new AtomicInteger(0); - for (int i = 0; i < NUM_OF_TASKS; ++i) { - executor.execute(new Runnable() { - @Override - public void run() { - v.incrementAndGet(); - } - }); - } - - for (int i = 0; i < NUM_OF_TASKS; ++i) { - executor.executePrioritized(new Runnable() { - @Override - public void run() { - v.incrementAndGet(); - } - }); - } - - try { - Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS); - } catch (InterruptedException e) { - Log.d(TAG, "Exception while sleeping.", e); - } - - assertEquals(2 * NUM_OF_TASKS, v.get()); - } -} |