diff options
Diffstat (limited to 'java/src')
15 files changed, 326 insertions, 118 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index 85dfea4e7..d35c8fae1 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -108,10 +108,9 @@ public class Keyboard { mAltCodeKeysWhileTyping = Collections.unmodifiableList(params.mAltCodeKeysWhileTyping); mIconsSet = params.mIconsSet; - mProximityInfo = new ProximityInfo(params.mId.mLocale.toString(), - params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight, - mMostCommonKeyWidth, mMostCommonKeyHeight, mSortedKeys, - params.mTouchPositionCorrection); + mProximityInfo = new ProximityInfo(params.GRID_WIDTH, params.GRID_HEIGHT, + mOccupiedWidth, mOccupiedHeight, mMostCommonKeyWidth, mMostCommonKeyHeight, + mSortedKeys, params.mTouchPositionCorrection); mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled; } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 246d11bac..7f2957fff 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -254,8 +254,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } public void onToggleEmojiKeyboard() { - if (mKeyboardLayoutSet == null || !isShowingEmojiPalettes()) { - mLatinIME.startShowingInputView(); + final boolean needsToLoadKeyboard = (mKeyboardLayoutSet == null); + if (needsToLoadKeyboard || !isShowingEmojiPalettes()) { + mLatinIME.startShowingInputView(needsToLoadKeyboard); setEmojiKeyboard(); } else { mLatinIME.stopShowingInputView(); diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index c19cd671a..9c5abcd71 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -52,18 +52,11 @@ public class ProximityInfo { private final int mMostCommonKeyHeight; private final List<Key> mSortedKeys; private final List<Key>[] mGridNeighbors; - private final String mLocaleStr; @SuppressWarnings("unchecked") - ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight, - final int minWidth, final int height, final int mostCommonKeyWidth, - final int mostCommonKeyHeight, final List<Key> sortedKeys, + ProximityInfo(final int gridWidth, final int gridHeight, final int minWidth, final int height, + final int mostCommonKeyWidth, final int mostCommonKeyHeight, final List<Key> sortedKeys, final TouchPositionCorrection touchPositionCorrection) { - if (TextUtils.isEmpty(localeStr)) { - mLocaleStr = ""; - } else { - mLocaleStr = localeStr; - } mGridWidth = gridWidth; mGridHeight = gridHeight; mGridSize = mGridWidth * mGridHeight; @@ -89,11 +82,10 @@ public class ProximityInfo { } // TODO: Stop passing proximityCharsArray - private static native long setProximityInfoNative(String locale, - int displayWidth, int displayHeight, int gridWidth, int gridHeight, - int mostCommonKeyWidth, int mostCommonKeyHeight, int[] proximityCharsArray, - int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, int[] keyWidths, - int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs, + private static native long setProximityInfoNative(int displayWidth, int displayHeight, + int gridWidth, int gridHeight, int mostCommonKeyWidth, int mostCommonKeyHeight, + int[] proximityCharsArray, int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, + int[] keyWidths, int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs, float[] sweetSpotCenterYs, float[] sweetSpotRadii); private static native void releaseProximityInfoNative(long nativeProximityInfo); @@ -221,10 +213,10 @@ public class ProximityInfo { } // TODO: Stop passing proximityCharsArray - return setProximityInfoNative(mLocaleStr, mKeyboardMinWidth, mKeyboardHeight, - mGridWidth, mGridHeight, mMostCommonKeyWidth, mMostCommonKeyHeight, - proximityCharsArray, keyCount, keyXCoordinates, keyYCoordinates, keyWidths, - keyHeights, keyCharCodes, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); + return setProximityInfoNative(mKeyboardMinWidth, mKeyboardHeight, mGridWidth, mGridHeight, + mMostCommonKeyWidth, mMostCommonKeyHeight, proximityCharsArray, keyCount, + keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes, + sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); } public long getNativeProximityInfo() { diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index 6dc1e8273..1f0317288 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -99,6 +99,30 @@ public class DictionaryFacilitator { DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length); /** + * Returns whether this facilitator is exactly for this list of locales. + * @param locales the list of locales to test against + * @return true if this facilitator handles exactly this list of locales, false otherwise + */ + public boolean isForLocales(final Locale[] locales) { + if (locales.length != mDictionaryGroups.length) { + return false; + } + for (final Locale locale : locales) { + boolean found = false; + for (final DictionaryGroup group : mDictionaryGroups) { + if (locale.equals(group.mLocale)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + + /** * A group of dictionaries that work together for a single language. */ private static class DictionaryGroup { @@ -199,6 +223,18 @@ public class DictionaryFacilitator { return mDictionaryGroups[0].mLocale; } + /** + * Returns the primary locale among all currently active locales. BE CAREFUL using this. + * + * DO NOT USE THIS just because it's convenient. Use it when it's correct, for example when + * choosing what dictionary to put a word in, or when changing the capitalization of a typed + * string. + * @return the primary active locale + */ + public Locale getPrimaryLocale() { + return mDictionaryGroups[0].mLocale; + } + private static ExpandableBinaryDictionary getSubDict(final String dictType, final Context context, final Locale locale, final File dictFile, final String dictNamePrefix) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 099d4850e..be2efb2dc 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -84,12 +84,12 @@ 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; import com.android.inputmethod.latin.utils.CursorAnchorInfoUtils; import com.android.inputmethod.latin.utils.DialogUtils; -import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions; import com.android.inputmethod.latin.utils.ImportantNoticeUtils; import com.android.inputmethod.latin.utils.IntentUtils; import com.android.inputmethod.latin.utils.JniUtils; @@ -102,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; @@ -177,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<LatinIME> { @@ -253,12 +256,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // We need to re-evaluate the currently composing word in case the script has // changed. postWaitForDictionaryLoad(); - latinIme.resetSuggest(); + 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(); @@ -537,9 +542,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.onCreate(); DEBUG = DebugFlags.DEBUG_ENABLED; - // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}. + // TODO: Resolve mutual dependencies of {@link #loadSettings()} and + // {@link #resetDictionaryFacilitatorIfNecessary()}. loadSettings(); - resetSuggest(); + resetDictionaryFacilitatorIfNecessary(); // Register to receive ringer mode change and network state change. // Also receive installation and removal of a dictionary pack. @@ -580,7 +586,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // been displayed. Opening dictionaries never affects responsivity as dictionaries are // asynchronously loaded. if (!mHandler.hasPendingReopenDictionaries()) { - resetSuggestForLocale(locale); + resetDictionaryFacilitatorForLocale(locale); } mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList( true /* allowsImplicitlySelectedSubtypes */)); @@ -621,8 +627,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void resetSuggest() { + private void resetDictionaryFacilitatorIfNecessary() { final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); + if (mDictionaryFacilitator.isForLocales(new Locale[] { switcherSubtypeLocale })) { + return; + } final String switcherLocaleStr = switcherSubtypeLocale.toString(); final Locale subtypeLocale; if (TextUtils.isEmpty(switcherLocaleStr)) { @@ -637,15 +646,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { subtypeLocale = switcherSubtypeLocale; } - resetSuggestForLocale(subtypeLocale); + resetDictionaryFacilitatorForLocale(subtypeLocale); } /** - * Reset suggest by loading dictionaries for the locale and the current settings values. + * Reset the facilitator by loading dictionaries for the locale and the current settings values. * * @param locale the locale */ - private void resetSuggestForLocale(final Locale locale) { + // TODO: make sure the current settings always have the right locale, and read from them + private void resetDictionaryFacilitatorForLocale(final Locale locale) { final SettingsValues settingsValues = mSettings.getCurrent(); mDictionaryFacilitator.resetDictionaries(this /* context */, locale, settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, @@ -723,6 +733,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void setInputView(final View view) { super.setInputView(view); mInputView = view; + updateSoftInputWindowLayoutParameters(); mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view); if (hasSuggestionStripView()) { mSuggestionStripView.setListener(this, view); @@ -795,6 +806,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen StatsUtils.onFinishInputView(); mHandler.onFinishInputView(finishingInput); mStatsUtilsManager.onFinishInputView(); + mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER; } @Override @@ -820,6 +832,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(); @@ -863,6 +878,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()) { @@ -901,12 +922,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.startInput(mSubtypeSwitcher.getCombiningRulesExtraValueOfCurrentSubtype(), currentSettingsValues); - // Note: the following does a round-trip IPC on the main thread: be careful - final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); - if (null != currentLocale && !currentLocale.equals(suggest.getLocale())) { - // TODO: Do this automatically. - resetSuggest(); - } + resetDictionaryFacilitatorIfNecessary(); // TODO[IL]: Can the following be moved to InputLogic#startInput? if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( @@ -1132,6 +1148,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onComputeInsets(final InputMethodService.Insets outInsets) { super.onComputeInsets(outInsets); + // This method may be called before {@link #setInputView(View)}. + if (mInputView == null) { + return; + } final SettingsValues settingsValues = mSettings.getCurrent(); final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView(); if (visibleKeyboardView == null || !hasSuggestionStripView()) { @@ -1166,12 +1186,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen outInsets.visibleTopInsets = visibleTopY; } - public void startShowingInputView() { + public void startShowingInputView(final boolean needsToLoadKeyboard) { mIsExecutingStartShowingInputView = true; // This {@link #showWindow(boolean)} will eventually call back // {@link #onEvaluateInputViewShown()}. showWindow(true /* showInput */); mIsExecutingStartShowingInputView = false; + if (needsToLoadKeyboard) { + loadKeyboard(); + } } public void stopShowingInputView() { @@ -1179,6 +1202,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } @Override + public boolean onShowInputRequested(final int flags, final boolean configChange) { + if (Settings.getInstance().getCurrent().mHasHardwareKeyboard) { + return true; + } + return super.onShowInputRequested(flags, configChange); + } + + @Override public boolean onEvaluateInputViewShown() { if (mIsExecutingStartShowingInputView) { return true; @@ -1208,8 +1239,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void updateFullscreenMode() { + super.updateFullscreenMode(); + mInputLogic.onUpdateFullscreenMode(isFullscreenMode()); + updateSoftInputWindowLayoutParameters(); + } + + private void updateSoftInputWindowLayoutParameters() { // Override layout parameters to expand {@link SoftInputWindow} to the entire screen. - // See {@link InputMethodService#setinputView(View) and + // See {@link InputMethodService#setinputView(View)} and // {@link SoftInputWindow#updateWidthHeight(WindowManager.LayoutParams)}. final Window window = getWindow().getWindow(); ViewLayoutUtils.updateLayoutHeightOf(window, LayoutParams.MATCH_PARENT); @@ -1228,8 +1265,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM); ViewLayoutUtils.updateLayoutHeightOf(mInputView, layoutHeight); } - super.updateFullscreenMode(); - mInputLogic.onUpdateFullscreenMode(isFullscreenMode()); } private int getCurrentAutoCapsState() { @@ -1398,6 +1433,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 @@ -1408,11 +1446,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. + * <p> + * 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. @@ -1554,7 +1606,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } final String wordToShow; if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) { - wordToShow = word.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale()); + wordToShow = word.toLowerCase(mDictionaryFacilitator.getPrimaryLocale()); } else { wordToShow = word; } @@ -1840,7 +1892,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void dumpDictionaryForDebug(final String dictName) { if (mDictionaryFacilitator.getLocale() == null) { - resetSuggest(); + resetDictionaryFacilitatorIfNecessary(); } mDictionaryFacilitator.dumpDictionaryForDebug(dictName); } 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/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index 2661d5d48..34edfa0da 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -163,13 +163,15 @@ public final class FormatSpec { static final int NOT_A_VERSION_NUMBER = -1; // These MUST have the same values as the relevant constants in format_utils.h. - // From version 4 on, we use version * 100 + revision as a version number. That allows + // From version 2.01 on, we use version * 100 + revision as a version number. That allows // us to change the format during development while having testing devices remove // older files with each upgrade, while still having a readable versioning scheme. // When we bump up the dictionary format version, we should update // ExpandableDictionary.needsToMigrateDictionary() and // ExpandableDictionary.matchesExpectedBinaryDictFormatVersionForThisType(). public static final int VERSION2 = 2; + public static final int VERSION201 = 201; + public static final int MINIMUM_SUPPORTED_VERSION_OF_CODE_POINT_TABLE = VERSION201; // Dictionary version used for testing. public static final int VERSION4_ONLY_FOR_TESTING = 399; public static final int VERSION401 = 401; diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java index 46705f9db..a180d060e 100644 --- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java +++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java @@ -54,11 +54,15 @@ public final class WordProperty implements Comparable<WordProperty> { mWord = word; mProbabilityInfo = probabilityInfo; mShortcutTargets = shortcutTargets; - mNgrams = new ArrayList<>(); - final NgramContext ngramContext = new NgramContext(new WordInfo(mWord)); - if (bigrams != null) { - for (final WeightedString bigramTarget : bigrams) { - mNgrams.add(new NgramProperty(bigramTarget, ngramContext)); + if (null == bigrams) { + mNgrams = null; + } else { + mNgrams = new ArrayList<>(); + final NgramContext ngramContext = new NgramContext(new WordInfo(mWord)); + if (bigrams != null) { + for (final WeightedString bigramTarget : bigrams) { + mNgrams.add(new NgramProperty(bigramTarget, ngramContext)); + } } } mIsBeginningOfSentence = false; @@ -87,7 +91,7 @@ public final class WordProperty implements Comparable<WordProperty> { mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints); mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo); mShortcutTargets = new ArrayList<>(); - mNgrams = new ArrayList<>(); + final ArrayList<NgramProperty> ngrams = new ArrayList<>(); mIsBeginningOfSentence = isBeginningOfSentence; mIsNotAWord = isNotAWord; mIsBlacklistEntry = isBlacklisted; @@ -104,8 +108,9 @@ public final class WordProperty implements Comparable<WordProperty> { final WeightedString ngramTarget = new WeightedString(ngramTargetString, createProbabilityInfoFromArray(bigramProbabilityInfo.get(i))); // TODO: Support n-gram. - mNgrams.add(new NgramProperty(ngramTarget, ngramContext)); + ngrams.add(new NgramProperty(ngramTarget, ngramContext)); } + mNgrams = ngrams.isEmpty() ? null : ngrams; final int shortcutTargetCount = shortcutTargets.size(); for (int i = 0; i < shortcutTargetCount; i++) { @@ -118,6 +123,9 @@ public final class WordProperty implements Comparable<WordProperty> { // TODO: Remove public ArrayList<WeightedString> getBigrams() { + if (null == mNgrams) { + return null; + } final ArrayList<WeightedString> bigrams = new ArrayList<>(); for (final NgramProperty ngram : mNgrams) { if (ngram.mNgramContext.getPrevWordCount() == 1) { @@ -167,11 +175,18 @@ public final class WordProperty implements Comparable<WordProperty> { if (!(o instanceof WordProperty)) return false; WordProperty w = (WordProperty)o; return mProbabilityInfo.equals(w.mProbabilityInfo) && mWord.equals(w.mWord) - && mShortcutTargets.equals(w.mShortcutTargets) && mNgrams.equals(w.mNgrams) + && mShortcutTargets.equals(w.mShortcutTargets) && equals(mNgrams, w.mNgrams) && mIsNotAWord == w.mIsNotAWord && mIsBlacklistEntry == w.mIsBlacklistEntry && mHasNgrams == w.mHasNgrams && mHasShortcuts && w.mHasNgrams; } + private <T> boolean equals(final ArrayList<T> a, final ArrayList<T> b) { + if (null == a) { + return null == b; + } + return a.equals(b); + } + @Override public int hashCode() { if (mHashCode == 0) { diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java index 51c4b1ee8..9ddee8629 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java @@ -145,9 +145,8 @@ public class SentenceLevelAdapter { int wordEnd = wordIterator.getEndOfWord(originalText, wordStart); while (wordStart <= end && wordEnd != -1 && wordStart != -1) { if (wordEnd >= start && wordEnd > wordStart) { - CharSequence subSequence = originalText.subSequence(wordStart, wordEnd).toString(); - final TextInfo ti = TextInfoCompatUtils.newInstance(subSequence, 0, - subSequence.length(), cookie, subSequence.hashCode()); + final TextInfo ti = TextInfoCompatUtils.newInstance(originalText, wordStart, + wordEnd, cookie, originalText.subSequence(wordStart, wordEnd).hashCode()); wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd)); } wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd); diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index 839fce051..a651774c6 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -60,6 +60,9 @@ import com.android.inputmethod.latin.utils.ViewLayoutUtils; import java.util.ArrayList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + final class SuggestionStripLayoutHelper { private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f; @@ -213,15 +216,14 @@ final class SuggestionStripLayoutHelper { return word; } - final int len = word.length(); final Spannable spannedWord = new SpannableString(word); final int options = mSuggestionStripOptions; if ((isAutoCorrection && (options & AUTO_CORRECT_BOLD) != 0) || (isTypedWordValid && (options & VALID_TYPED_WORD_BOLD) != 0)) { - spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + addStyleSpan(spannedWord, BOLD_SPAN); } if (isAutoCorrection && (options & AUTO_CORRECT_UNDERLINE) != 0) { - spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + addStyleSpan(spannedWord, UNDERLINE_SPAN); } return spannedWord; } @@ -446,10 +448,11 @@ final class SuggestionStripLayoutHelper { // {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack. // Use a simple {@link String} to avoid the issue. wordView.setContentDescription(TextUtils.isEmpty(word) ? null : word.toString()); - final CharSequence text = getEllipsizedText(word, width, wordView.getPaint()); - final float scaleX = getTextScaleX(word, width, wordView.getPaint()); + final CharSequence text = getEllipsizedTextWithSettingScaleX( + word, width, wordView.getPaint()); + final float scaleX = wordView.getTextScaleX(); wordView.setText(text); // TextView.setText() resets text scale x to 1.0. - wordView.setTextScaleX(Math.max(scaleX, MIN_TEXT_XSCALE)); + wordView.setTextScaleX(scaleX); // A <code>wordView</code> should be disabled when <code>word</code> is empty in order to // make it unclickable. // With accessibility touch exploration on, <code>wordView</code> should be enabled even @@ -562,7 +565,8 @@ final class SuggestionStripLayoutHelper { final TextView wordView = (TextView)addToDictionaryStrip.findViewById(R.id.word_to_save); wordView.setTextColor(mColorTypedWord); final int wordWidth = (int)(width * mCenterSuggestionWeight); - final CharSequence wordToSave = getEllipsizedText(word, wordWidth, wordView.getPaint()); + final CharSequence wordToSave = getEllipsizedTextWithSettingScaleX( + word, wordWidth, wordView.getPaint()); final float wordScaleX = wordView.getTextScaleX(); wordView.setText(wordToSave); wordView.setTextScaleX(wordScaleX); @@ -596,7 +600,7 @@ final class SuggestionStripLayoutHelper { } hintView.setTextColor(mColorAutoCorrect); final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); - hintView.setText(hintText); + hintView.setText(hintText); // TextView.setText() resets text scale x to 1.0. hintView.setTextScaleX(hintScaleX); setLayoutWeight(hintView, hintWeight, ViewGroup.LayoutParams.MATCH_PARENT); } @@ -608,8 +612,7 @@ final class SuggestionStripLayoutHelper { final int width = titleView.getWidth() - titleView.getPaddingLeft() - titleView.getPaddingRight(); titleView.setTextColor(mColorAutoCorrect); - titleView.setText(importantNoticeTitle); - titleView.setTextScaleX(1.0f); // Reset textScaleX. + titleView.setText(importantNoticeTitle); // TextView.setText() resets text scale x to 1.0. final float titleScaleX = getTextScaleX(importantNoticeTitle, width, titleView.getPaint()); titleView.setTextScaleX(titleScaleX); } @@ -624,18 +627,19 @@ final class SuggestionStripLayoutHelper { } } - private static float getTextScaleX(final CharSequence text, final int maxWidth, + private static float getTextScaleX(@Nullable final CharSequence text, final int maxWidth, final TextPaint paint) { paint.setTextScaleX(1.0f); final int width = getTextWidth(text, paint); if (width <= maxWidth || maxWidth <= 0) { return 1.0f; } - return maxWidth / (float)width; + return maxWidth / (float) width; } - private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth, - final TextPaint paint) { + @Nullable + private static CharSequence getEllipsizedTextWithSettingScaleX( + @Nullable final CharSequence text, final int maxWidth, @Nonnull final TextPaint paint) { if (text == null) { return null; } @@ -645,62 +649,63 @@ final class SuggestionStripLayoutHelper { return text; } - // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To - // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE). - final float upscaledWidth = maxWidth / MIN_TEXT_XSCALE; - CharSequence ellipsized = TextUtils.ellipsize( - text, paint, upscaledWidth, TextUtils.TruncateAt.MIDDLE); - // For an unknown reason, ellipsized seems to return a text that does indeed fit inside the - // passed width according to paint.measureText, but not according to paint.getTextWidths. - // But when rendered, the text seems to actually take up as many pixels as returned by - // paint.getTextWidths, hence problem. - // To save this case, we compare the measured size of the new text, and if it's too much, - // try it again removing the difference. This may still give a text too long by one or - // two pixels so we take an additional 2 pixels cushion and call it a day. - // TODO: figure out why getTextWidths and measureText don't agree with each other, and - // remove the following code. - final float ellipsizedTextWidth = getTextWidth(ellipsized, paint); - if (upscaledWidth <= ellipsizedTextWidth) { - ellipsized = TextUtils.ellipsize( - text, paint, upscaledWidth - (ellipsizedTextWidth - upscaledWidth) - 2, - TextUtils.TruncateAt.MIDDLE); - } + // <code>text</code> must be ellipsized with minimum text scale x. paint.setTextScaleX(MIN_TEXT_XSCALE); - return ellipsized; + final boolean hasBoldStyle = hasStyleSpan(text, BOLD_SPAN); + final boolean hasUnderlineStyle = hasStyleSpan(text, UNDERLINE_SPAN); + // TextUtils.ellipsize erases any span object existed after ellipsized point. + // We have to restore these spans afterward. + final CharSequence ellipsizedText = TextUtils.ellipsize( + text, paint, maxWidth, TextUtils.TruncateAt.MIDDLE); + if (!hasBoldStyle && !hasUnderlineStyle) { + return ellipsizedText; + } + final Spannable spannableText = (ellipsizedText instanceof Spannable) + ? (Spannable)ellipsizedText : new SpannableString(ellipsizedText); + if (hasBoldStyle) { + addStyleSpan(spannableText, BOLD_SPAN); + } + if (hasUnderlineStyle) { + addStyleSpan(spannableText, UNDERLINE_SPAN); + } + return spannableText; + } + + private static boolean hasStyleSpan(@Nullable final CharSequence text, + final CharacterStyle style) { + if (text instanceof Spanned) { + return ((Spanned)text).getSpanStart(style) >= 0; + } + return false; + } + + private static void addStyleSpan(@Nonnull final Spannable text, final CharacterStyle style) { + text.removeSpan(style); + text.setSpan(style, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); } - private static int getTextWidth(final CharSequence text, final TextPaint paint) { + private static int getTextWidth(@Nullable final CharSequence text, final TextPaint paint) { if (TextUtils.isEmpty(text)) { return 0; } + final int length = text.length(); + final float[] widths = new float[length]; + final int count; final Typeface savedTypeface = paint.getTypeface(); - paint.setTypeface(getTextTypeface(text)); - final int len = text.length(); - final float[] widths = new float[len]; - final int count = paint.getTextWidths(text, 0, len, widths); + try { + paint.setTypeface(getTextTypeface(text)); + count = paint.getTextWidths(text, 0, length, widths); + } finally { + paint.setTypeface(savedTypeface); + } int width = 0; for (int i = 0; i < count; i++) { width += Math.round(widths[i] + 0.5f); } - paint.setTypeface(savedTypeface); return width; } - private static Typeface getTextTypeface(final CharSequence text) { - if (!(text instanceof SpannableString)) { - return Typeface.DEFAULT; - } - - final SpannableString ss = (SpannableString)text; - final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class); - if (styles.length == 0) { - return Typeface.DEFAULT; - } - - if (styles[0].getStyle() == Typeface.BOLD) { - return Typeface.DEFAULT_BOLD; - } - // TODO: BOLD_ITALIC, ITALIC case? - return Typeface.DEFAULT; + private static Typeface getTextTypeface(@Nullable final CharSequence text) { + return hasStyleSpan(text, BOLD_SPAN) ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT; } } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index b421a7eb5..e40fd8800 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -400,6 +400,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick if (mStripVisibilityGroup.isShowingImportantNoticeStrip()) { return false; } + // Detecting sliding up finger to show {@link MoreSuggestionsView}. if (!mMoreSuggestionsView.isShowingInParent()) { mLastX = (int)me.getX(); mLastY = (int)me.getY(); @@ -439,6 +440,11 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick @Override public boolean onTouchEvent(final MotionEvent me) { + if (!mMoreSuggestionsView.isShowingInParent()) { + // Ignore any touch event while more suggestions panel hasn't been shown. + // Detecting sliding up is done at {@link #onInterceptTouchEvent}. + return true; + } // In the sliding input mode. {@link MotionEvent} should be forwarded to // {@link MoreSuggestionsView}. final int index = me.getActionIndex(); 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(); + } } |