From c92c883fdf2287b49392692fa2e8d109dc26f785 Mon Sep 17 00:00:00 2001 From: David Faden Date: Thu, 4 Sep 2014 13:42:10 -0700 Subject: [PATCH] Add calls to stub for API to consume gesture data. Bug: 17400259 Change-Id: Ib3511afffe1d14662e7dd14611f384689516e664 --- .../touchinputconsumer/GestureConsumer.java | 66 +++++++++++++++++++ .../android/inputmethod/latin/LatinIME.java | 35 +++++++++- .../latin/RichInputConnection.java | 13 +++- .../inputmethod/latin/WordComposer.java | 3 +- .../latin/inputlogic/InputLogic.java | 41 ++++++++++++ .../inputlogic/PrivateCommandPerformer.java | 40 +++++++++++ .../latin/utils/CollectionUtils.java | 10 +++ .../NullGestureConsumerTests.java | 47 +++++++++++++ .../latin/utils/CollectionUtilsTests.java | 55 ++++++++++++++++ 9 files changed, 306 insertions(+), 4 deletions(-) create mode 100644 java-overridable/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java create mode 100644 java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java create mode 100644 tests/src/com/android/inputmethod/latin/touchinputconsumer/NullGestureConsumerTests.java create mode 100644 tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java diff --git a/java-overridable/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java b/java-overridable/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java new file mode 100644 index 000000000..672d6d1a1 --- /dev/null +++ b/java-overridable/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.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.touchinputconsumer; + +import android.view.inputmethod.EditorInfo; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.InputPointers; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer; + +import java.util.List; +import java.util.Locale; + +/** + * Stub for GestureConsumer. + *
+ * The methods of this class should only be called from a single thread, e.g., + * the UI Thread. + */ +public class GestureConsumer { + public static final GestureConsumer NULL_GESTURE_CONSUMER = + new GestureConsumer(); + + public static GestureConsumer newInstance( + final EditorInfo editorInfo, final PrivateCommandPerformer commandPerformer, + final List locales, final Keyboard keyboard) { + return GestureConsumer.NULL_GESTURE_CONSUMER; + } + + private GestureConsumer() { + } + + public boolean willConsume() { + return false; + } + + public void onInit(final List locales, final Keyboard keyboard) { + } + + public void onGestureStarted(final List locales, final Keyboard keyboard) { + } + + public void onGestureCanceled() { + } + + public void onGestureCompleted(final InputPointers inputPointers) { + } + + public void onImeSuggestionsProcessed(final SuggestedWords suggestedWords, + final int composingStart, final int composingLength) { + } +} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 714f62f3e..861a8903e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -84,6 +84,7 @@ import com.android.inputmethod.latin.settings.SettingsActivity; import com.android.inputmethod.latin.settings.SettingsValues; import com.android.inputmethod.latin.suggestions.SuggestionStripView; import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; +import com.android.inputmethod.latin.touchinputconsumer.GestureConsumer; import com.android.inputmethod.latin.utils.ApplicationUtils; import com.android.inputmethod.latin.utils.CapsModeUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; @@ -101,6 +102,7 @@ import com.android.inputmethod.latin.utils.ViewLayoutUtils; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -176,6 +178,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private final boolean mIsHardwareAcceleratedDrawingEnabled; + private GestureConsumer mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; + public final UIHandler mHandler = new UIHandler(this); public static final class UIHandler extends LeakGuardHandlerWrapper { @@ -255,9 +259,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen latinIme.resetDictionaryFacilitatorIfNecessary(); break; case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED: + final SuggestedWords suggestedWords = (SuggestedWords) msg.obj; latinIme.mInputLogic.onUpdateTailBatchInputCompleted( latinIme.mSettings.getCurrent(), - (SuggestedWords) msg.obj, latinIme.mKeyboardSwitcher); + suggestedWords, latinIme.mKeyboardSwitcher); + latinIme.onTailBatchInputResultShown(suggestedWords); break; case MSG_RESET_CACHES: final SettingsValues settingsValues = latinIme.mSettings.getCurrent(); @@ -799,6 +805,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen StatsUtils.onFinishInputView(); mHandler.onFinishInputView(finishingInput); mStatsUtilsManager.onFinishInputView(); + mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; } @Override @@ -824,6 +831,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @SuppressWarnings("deprecation") private void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) { super.onStartInputView(editorInfo, restarting); + // Switch to the null consumer to handle cases leading to early exit below, for which we + // also wouldn't be consuming gesture data. + mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; mRichImm.clearSubtypeCaches(); final KeyboardSwitcher switcher = mKeyboardSwitcher; switcher.updateKeyboardTheme(); @@ -867,6 +877,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } + // Update to a gesture consumer with the current editor and IME state. + mGestureConsumer = GestureConsumer.newInstance(editorInfo, + mInputLogic.getPrivateCommandPerformer(), + Collections.singletonList(mSubtypeSwitcher.getCurrentSubtypeLocale()), + switcher.getKeyboard()); + // Forward this event to the accessibility utilities, if enabled. final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance(); if (accessUtils.isTouchExplorationEnabled()) { @@ -1397,6 +1413,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onStartBatchInput() { mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler); + mGestureConsumer.onGestureStarted( + Collections.singletonList(mSubtypeSwitcher.getCurrentSubtypeLocale()), + mKeyboardSwitcher.getKeyboard()); } @Override @@ -1407,11 +1426,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onEndBatchInput(final InputPointers batchPointers) { mInputLogic.onEndBatchInput(batchPointers); + mGestureConsumer.onGestureCompleted(batchPointers); } @Override public void onCancelBatchInput() { mInputLogic.onCancelBatchInput(mHandler); + mGestureConsumer.onGestureCanceled(); + } + + /** + * To be called after the InputLogic has gotten a chance to act on the on-device decoding + * for the full gesture, possibly updating the TextView to reflect the first decoding. + *

+ * This method must be run on the UI Thread. + * @param suggestedWords On-device decoding for the full gesture. + */ + public void onTailBatchInputResultShown(final SuggestedWords suggestedWords) { + mGestureConsumer.onImeSuggestionsProcessed(suggestedWords, + mInputLogic.getComposingStart(), mInputLogic.getComposingLength()); } // This method must run on the UI Thread. diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index a7ea2a1c8..62a258b20 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin; import android.graphics.Color; import android.inputmethodservice.InputMethodService; import android.os.Build; +import android.os.Bundle; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; @@ -33,6 +34,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import com.android.inputmethod.compat.InputConnectionCompatUtils; +import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; import com.android.inputmethod.latin.utils.CapsModeUtils; import com.android.inputmethod.latin.utils.DebugLogUtils; @@ -52,7 +54,7 @@ import java.util.Arrays; * all the time to find out what text is in the buffer, when we need it to determine caps mode * for example. */ -public final class RichInputConnection { +public final class RichInputConnection implements PrivateCommandPerformer { private static final String TAG = RichInputConnection.class.getSimpleName(); private static final boolean DBG = false; private static final boolean DEBUG_PREVIOUS_TEXT = false; @@ -896,6 +898,15 @@ public final class RichInputConnection { } } + @Override + public boolean performPrivateCommand(final String action, final Bundle data) { + mIC = mParent.getCurrentInputConnection(); + if (mIC == null) { + return false; + } + return mIC.performPrivateCommand(action, data); + } + public int getExpectedSelectionStart() { return mExpectedSelStart; } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index f85b34b5e..157bd1565 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -128,8 +128,7 @@ public final class WordComposer { * Number of keystrokes in the composing word. * @return the number of keystrokes */ - // This may be made public if need be, but right now it's not used anywhere - /* package for tests */ int size() { + public int size() { return mCodePointSize; } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index b4a1c3e65..1b1d5e7e5 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -2263,6 +2263,47 @@ public final class InputLogic { mConnection.setComposingText(composingTextToBeSet, newCursorPosition); } + /** + * Gets an object allowing private IME commands to be sent to the + * underlying editor. + * @return An object for sending private commands to the underlying editor. + */ + public PrivateCommandPerformer getPrivateCommandPerformer() { + return mConnection; + } + + /** + * Gets the expected index of the first char of the composing span within the editor's text. + * Returns a negative value in case there appears to be no valid composing span. + * + * @see #getComposingLength() + * @see RichInputConnection#hasSelection() + * @see RichInputConnection#isCursorPositionKnown() + * @see RichInputConnection#getExpectedSelectionStart() + * @see RichInputConnection#getExpectedSelectionEnd() + * @return The expected index in Java chars of the first char of the composing span. + */ + // TODO: try and see if we can get rid of this method. Ideally the users of this class should + // never need to know this. + public int getComposingStart() { + if (!mConnection.isCursorPositionKnown() || mConnection.hasSelection()) { + return -1; + } + return mConnection.getExpectedSelectionStart() - mWordComposer.size(); + } + + /** + * Gets the expected length in Java chars of the composing span. + * May be 0 if there is no valid composing span. + * @see #getComposingStart() + * @return The expected length of the composing span. + */ + // TODO: try and see if we can get rid of this method. Ideally the users of this class should + // never need to know this. + public int getComposingLength() { + return mWordComposer.size(); + } + ////////////////////////////////////////////////////////////////////////////////////////////// // Following methods are tentatively placed in this class for the integration with // TextDecorator. diff --git a/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java b/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java new file mode 100644 index 000000000..42eaa9c82 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.inputlogic; + +import android.os.Bundle; + +/** + * Provides an interface matching + * {@link android.view.inputmethod.InputConnection#performPrivateCommand(String,Bundle)}. + */ +public interface PrivateCommandPerformer { + /** + * API to send private commands from an input method to its connected + * editor. This can be used to provide domain-specific features that are + * only known between certain input methods and their clients. + * + * @param action Name of the command to be performed. This must be a scoped + * name, i.e. prefixed with a package name you own, so that + * different developers will not create conflicting commands. + * @param data Any data to include with the command. + * @return true if the command was sent (regardless of whether the + * associated editor understood it), false if the input connection is no + * longer valid. + */ + boolean performPrivateCommand(String action, Bundle data); +} diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java index 61292fc36..fb36b7c50 100644 --- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java @@ -17,6 +17,7 @@ package com.android.inputmethod.latin.utils; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.TreeMap; @@ -40,4 +41,13 @@ public final class CollectionUtils { } 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. + */ + public static boolean isNullOrEmpty(final Collection c) { + return c == null || c.isEmpty(); + } } diff --git a/tests/src/com/android/inputmethod/latin/touchinputconsumer/NullGestureConsumerTests.java b/tests/src/com/android/inputmethod/latin/touchinputconsumer/NullGestureConsumerTests.java new file mode 100644 index 000000000..ca1039bd9 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/touchinputconsumer/NullGestureConsumerTests.java @@ -0,0 +1,47 @@ +/* + * 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.touchinputconsumer; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +/** + * Tests for GestureConsumer.NULL_GESTURE_CONSUMER. + */ +@SmallTest +public class NullGestureConsumerTests extends AndroidTestCase { + /** + * Tests that GestureConsumer.NULL_GESTURE_CONSUMER indicates that it won't consume gesture data + * and that its methods don't raise exceptions even for invalid data. + */ + public void testNullGestureConsumer() { + assertFalse(GestureConsumer.NULL_GESTURE_CONSUMER.willConsume()); + GestureConsumer.NULL_GESTURE_CONSUMER.onInit(null, null); + GestureConsumer.NULL_GESTURE_CONSUMER.onGestureStarted(null, null); + GestureConsumer.NULL_GESTURE_CONSUMER.onGestureCanceled(); + GestureConsumer.NULL_GESTURE_CONSUMER.onGestureCompleted(null); + GestureConsumer.NULL_GESTURE_CONSUMER.onImeSuggestionsProcessed(null, -1, -1); + } + + /** + * Tests that newInstance returns NULL_GESTURE_CONSUMER for invalid input. + */ + public void testNewInstanceGivesNullGestureConsumerForInvalidInputs() { + assertSame(GestureConsumer.NULL_GESTURE_CONSUMER, + GestureConsumer.newInstance(null, null, null, null)); + } +} diff --git a/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java new file mode 100644 index 000000000..76e28288f --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.utils; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +/** + * Tests for {@link CollectionUtils}. + */ +@SmallTest +public class CollectionUtilsTests extends AndroidTestCase { + /** + * Tests that {@link CollectionUtils#arrayAsList(E[],int,int)} gives the expected + * results for a few valid inputs. + */ + public void testArrayAsList() { + final String[] array = { "0", "1", "2", "3", "4" }; + final ArrayList empty = new ArrayList<>(); + assertEquals(empty, CollectionUtils.arrayAsList(array, 0, 0)); + assertEquals(empty, CollectionUtils.arrayAsList(array, 1, 1)); + final ArrayList expected123 = new ArrayList<>(Arrays.asList("1", "2", "3")); + assertEquals(expected123, CollectionUtils.arrayAsList(array, 1, 4)); + } + + /** + * Tests that {@link CollectionUtils#isEmpty(java.util.Collection)} gives the expected + * results for a few cases. + */ + public void testIsNullOrEmpty() { + assertTrue(CollectionUtils.isNullOrEmpty(null)); + assertTrue(CollectionUtils.isNullOrEmpty(new ArrayList())); + assertTrue(CollectionUtils.isNullOrEmpty(Collections.EMPTY_SET)); + assertFalse(CollectionUtils.isNullOrEmpty(Collections.singleton("Not empty"))); + } + +}