diff options
Diffstat (limited to 'java/src')
20 files changed, 314 insertions, 127 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java index c28d72949..4366348d5 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java @@ -16,6 +16,8 @@ package com.android.inputmethod.dictionarypack; +import com.android.inputmethod.latin.utils.FragmentUtils; + import android.content.Intent; import android.os.Bundle; import android.preference.PreferenceActivity; @@ -45,6 +47,6 @@ public final class DictionarySettingsActivity extends PreferenceActivity { // TODO: Uncomment the override annotation once we start using SDK version 19. // @Override public boolean isValidFragment(String fragmentName) { - return fragmentName.equals(DEFAULT_FRAGMENT); + return FragmentUtils.isValidFragment(fragmentName); } } diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java index 9779c683c..f12373503 100644 --- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java +++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java @@ -162,9 +162,11 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange addShownCategoryId(CATEGORY_ID_OBJECTS); addShownCategoryId(CATEGORY_ID_NATURE); addShownCategoryId(CATEGORY_ID_PLACES); - mCurrentCategoryId = CATEGORY_ID_PEOPLE; + mCurrentCategoryId = + Settings.readLastShownEmojiCategoryId(mPrefs, CATEGORY_ID_PEOPLE); } else { - mCurrentCategoryId = CATEGORY_ID_SYMBOLS; + mCurrentCategoryId = + Settings.readLastShownEmojiCategoryId(mPrefs, CATEGORY_ID_SYMBOLS); } addShownCategoryId(CATEGORY_ID_SYMBOLS); addShownCategoryId(CATEGORY_ID_EMOTICONS); @@ -222,6 +224,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange public void setCurrentCategoryId(int categoryId) { mCurrentCategoryId = categoryId; + Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId); } public void setCurrentCategoryPageId(int id) { @@ -233,7 +236,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange } public void saveLastTypedCategoryPage() { - Settings.writeEmojiCategoryLastTypedId( + Settings.writeLastTypedEmojiCategoryPageId( mPrefs, mCurrentCategoryId, mCurrentCategoryPageId); } @@ -254,7 +257,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange // Returns the view pager's page position for the categoryId public int getPageIdFromCategoryId(int categoryId) { final int lastSavedCategoryPageId = - Settings.readEmojiCategoryLastTypedId(mPrefs, categoryId); + Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId); int sum = 0; for (int i = 0; i < mShownCategories.size(); ++i) { final CategoryProperties props = mShownCategories.get(i); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 97609837e..b7521b998 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -155,7 +155,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } public void saveKeyboardState() { - if (getKeyboard() != null || isShowingEmojiKeyboard()) { + if (getKeyboard() != null || isShowingEmojiPalettes()) { mState.onSaveKeyboardState(); } } @@ -316,19 +316,23 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState()); } - public boolean isShowingEmojiKeyboard() { - return mEmojiPalettesView != null && mEmojiPalettesView.getVisibility() == View.VISIBLE; + private boolean isShowingMainKeyboard() { + return null != mKeyboardView && mKeyboardView.isShown(); + } + + public boolean isShowingEmojiPalettes() { + return mEmojiPalettesView != null && mEmojiPalettesView.isShown(); } public boolean isShowingMoreKeysPanel() { - if (isShowingEmojiKeyboard()) { + if (isShowingEmojiPalettes()) { return false; } return mKeyboardView.isShowingMoreKeysPanel(); } public View getVisibleKeyboardView() { - if (isShowingEmojiKeyboard()) { + if (isShowingEmojiPalettes()) { return mEmojiPalettesView; } return mKeyboardView; @@ -348,6 +352,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } + public boolean isShowingMainKeyboardOrEmojiPalettes() { + return isShowingMainKeyboard() || isShowingEmojiPalettes(); + } + public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) { if (mKeyboardView != null) { mKeyboardView.closing(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java index 09766ac6c..3133e54be 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java @@ -184,12 +184,12 @@ public class DynamicGridKeyboard extends Keyboard { private int getKeyY0(final int index) { final int row = index / mColumnsNum; - return row * mVerticalStep; + return row * mVerticalStep + mVerticalGap / 2; } private int getKeyY1(final int index) { final int row = index / mColumnsNum + 1; - return row * mVerticalStep; + return row * mVerticalStep + mVerticalGap / 2; } @Override diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index 9f9fdaa6f..dd98c1703 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -327,6 +327,9 @@ public final class KeyboardState { } mIsAlphabetMode = false; mIsEmojiMode = true; + // Remember caps lock mode and reset alphabet shift state. + mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); + mAlphabetShiftState.setShiftLocked(false); mSwitchActions.setEmojiKeyboard(); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index bcb80b455..e769e3cdd 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -278,7 +278,7 @@ public final class KeyboardTextsSet { /* 50 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1", /* 51 */ "$", /* 52 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1", - /* 53 */ "!fixedColumnOrder!3,!,\\,,?,:,;,@", + /* 53 */ "!fixedColumnOrder!4,#,!,\\,,?,-,:,',@", // U+2020: "†" DAGGER // U+2021: "‡" DOUBLE DAGGER // U+2605: "★" BLACK STAR diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java index 642b3a4da..2e9280c77 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -16,8 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.latin.utils.StringUtils; - import android.text.TextUtils; /** @@ -85,8 +83,4 @@ public final class LastComposedWord { private boolean didCommitTypedWord() { return TextUtils.equals(mTypedWord, mCommittedWord); } - - public static int getSeparatorLength(final String separatorString) { - return StringUtils.codePointCount(separatorString); - } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 4d95ca3af..ee2cd7b5b 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -99,6 +99,7 @@ import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LatinImeLoggerUtils; import com.android.inputmethod.latin.utils.RecapitalizeStatus; import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.utils.StringUtils; import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; import com.android.inputmethod.latin.utils.TextRange; import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils; @@ -1261,9 +1262,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final boolean needsInputViewShown) { // TODO: Modify this if we support suggestions with hard keyboard if (onEvaluateInputViewShown() && mSuggestionStripView != null) { - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - final boolean inputViewShown = (mainKeyboardView != null) - ? mainKeyboardView.isShown() : false; + final boolean inputViewShown = mKeyboardSwitcher.isShowingMainKeyboardOrEmojiPalettes(); final boolean shouldShowSuggestions = shown && (needsInputViewShown ? inputViewShown : true); if (isFullscreenMode()) { @@ -1329,7 +1328,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (visibleKeyboardView.isShown()) { // Note that the height of Emoji layout is the same as the height of the main keyboard // and the suggestion strip - if (mKeyboardSwitcher.isShowingEmojiKeyboard() + if (mKeyboardSwitcher.isShowingEmojiPalettes() || mSuggestionStripView.getVisibility() == View.VISIBLE) { visibleTopY -= suggestionsHeight; } @@ -1588,8 +1587,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // relying on this behavior so we continue to support it for older apps. sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER); } else { - final String text = new String(new int[] { code }, 0, 1); - mConnection.commitText(text, text.length()); + mConnection.commitText(StringUtils.newSingleCodePointString(code), 1); } } @@ -1624,8 +1622,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case Constants.CODE_DELETE: mSpaceState = SPACE_STATE_NONE; handleBackspace(spaceState); - mDeleteCount++; - mExpectingUpdateSelection = true; LatinImeLogger.logOnDelete(x, y); break; case Constants.CODE_SHIFT: @@ -1712,7 +1708,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSpaceState = SPACE_STATE_NONE; final boolean didAutoCorrect; final SettingsValues settingsValues = mSettings.getCurrent(); - if (settingsValues.isWordSeparator(primaryCode)) { + if (settingsValues.isWordSeparator(primaryCode) + || Character.getType(primaryCode) == Character.OTHER_SYMBOL) { didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState); } else { didAutoCorrect = false; @@ -1838,12 +1835,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public boolean handleMessage(final Message msg) { + // TODO: straighten message passing - we don't need two kinds of messages calling + // each other. switch (msg.what) { case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP: - updateBatchInput((InputPointers)msg.obj); + updateBatchInput((InputPointers)msg.obj, msg.arg2 /* sequenceNumber */); break; case MSG_GET_SUGGESTED_WORDS: - mLatinIme.getSuggestedWords(msg.arg1, (OnGetSuggestedWordsCallback) msg.obj); + mLatinIme.getSuggestedWords(msg.arg1 /* sessionId */, + msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj); break; } return true; @@ -1860,14 +1860,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // Run in the Handler thread. - private void updateBatchInput(final InputPointers batchPointers) { + private void updateBatchInput(final InputPointers batchPointers, final int sequenceNumber) { synchronized (mLock) { if (!mInBatchInput) { // Batch input has ended or canceled while the message was being delivered. return; } - getSuggestedWordsGestureLocked(batchPointers, new OnGetSuggestedWordsCallback() { + getSuggestedWordsGestureLocked(batchPointers, sequenceNumber, + new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords(final SuggestedWords suggestedWords) { mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( @@ -1878,13 +1879,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // Run in the UI thread. - public void onUpdateBatchInput(final InputPointers batchPointers) { + public void onUpdateBatchInput(final InputPointers batchPointers, + final int sequenceNumber) { if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) { return; } - mHandler.obtainMessage( - MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, batchPointers) - .sendToTarget(); + mHandler.obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, 0 /* arg1 */, + sequenceNumber /* arg2 */, batchPointers /* obj */).sendToTarget(); } public void onCancelBatchInput() { @@ -1898,7 +1899,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Run in the UI thread. public void onEndBatchInput(final InputPointers batchPointers) { synchronized(mLock) { - getSuggestedWordsGestureLocked(batchPointers, new OnGetSuggestedWordsCallback() { + getSuggestedWordsGestureLocked(batchPointers, SuggestedWords.NOT_A_SEQUENCE_NUMBER, + new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords(final SuggestedWords suggestedWords) { mInBatchInput = false; @@ -1913,10 +1915,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to // be synchronized. private void getSuggestedWordsGestureLocked(final InputPointers batchPointers, - final OnGetSuggestedWordsCallback callback) { + final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { mLatinIme.mWordComposer.setBatchInputPointers(batchPointers); mLatinIme.getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_GESTURE, - new OnGetSuggestedWordsCallback() { + sequenceNumber, new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords(SuggestedWords suggestedWords) { final int suggestionCount = suggestedWords.size(); @@ -1931,9 +1933,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen }); } - public void getSuggestedWords(final int sessionId, + public void getSuggestedWords(final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { - mHandler.obtainMessage(MSG_GET_SUGGESTED_WORDS, sessionId, 0, callback).sendToTarget(); + mHandler.obtainMessage(MSG_GET_SUGGESTED_WORDS, sessionId, sequenceNumber, callback) + .sendToTarget(); } private void onDestroy() { @@ -1954,11 +1957,28 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } + /* The sequence number member is only used in onUpdateBatchInput. It is increased each time + * auto-commit happens. The reason we need this is, when auto-commit happens we trim the + * input pointers that are held in a singleton, and to know how much to trim we rely on the + * results of the suggestion process that is held in mSuggestedWords. + * However, the suggestion process is asynchronous, and sometimes we may enter the + * onUpdateBatchInput method twice without having recomputed suggestions yet, or having + * received new suggestions generated from not-yet-trimmed input pointers. In this case, the + * mIndexOfTouchPointOfSecondWords member will be out of date, and we must not use it lest we + * remove an unrelated number of pointers (possibly even more than are left in the input + * pointers, leading to a crash). + * To avoid that, we increase the sequence number each time we auto-commit and trim the + * input pointers, and we do not use any suggested words that have been generated with an + * earlier sequence number. + */ + private int mAutoCommitSequenceNumber = 1; @Override public void onUpdateBatchInput(final InputPointers batchPointers) { if (mSettings.getCurrent().mPhraseGestureEnabled) { final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate(); - if (null != candidate) { + // If these suggested words have been generated with out of date input pointers, then + // we skip auto-commit (see comments above on the mSequenceNumber member). + if (null != candidate && mSuggestedWords.mSequenceNumber >= mAutoCommitSequenceNumber) { if (candidate.mSourceDict.shouldAutoCommit(candidate)) { final String[] commitParts = candidate.mWord.split(" ", 2); batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord); @@ -1967,10 +1987,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSpaceState = SPACE_STATE_PHANTOM; mKeyboardSwitcher.updateShiftState(); mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode()); + ++mAutoCommitSequenceNumber; } } } - mInputUpdater.onUpdateBatchInput(batchPointers); + mInputUpdater.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber); } // This method must run in UI Thread. @@ -2052,6 +2073,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void handleBackspace(final int spaceState) { + // We revert these in this method if the deletion doesn't happen. + mDeleteCount++; + mExpectingUpdateSelection = true; + // In many cases, we may have to put the keyboard in auto-shift state again. However // we want to wait a few milliseconds before doing it to avoid the keyboard flashing // during key repeat. @@ -2141,8 +2166,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // This should never happen. Log.e(TAG, "Backspace when we don't know the selection position"); } - final int lengthToDelete = Character.isSupplementaryCodePoint( - mConnection.getCodePointBeforeCursor()) ? 2 : 1; + final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); + if (codePointBeforeCursor == Constants.NOT_A_CODE) { + // Nothing to delete before the cursor. We have to revert the deletion states + // that were updated at the beginning of this method. + mDeleteCount--; + mExpectingUpdateSelection = false; + return; + } + final int lengthToDelete = + Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1; if (mAppWorkAroundsUtils.isBeforeJellyBean() || currentSettings.mInputAttributes.isTypeNull()) { // There are two possible reasons to send a key event: either the field has @@ -2161,12 +2194,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen true /* shouldUncommitLogUnit */); } if (mDeleteCount > DELETE_ACCELERATE_AT) { - final int lengthToDeleteAgain = Character.isSupplementaryCodePoint( - mConnection.getCodePointBeforeCursor()) ? 2 : 1; - mConnection.deleteSurroundingText(lengthToDeleteAgain, 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain, - true /* shouldUncommitLogUnit */); + final int codePointBeforeCursorToDeleteAgain = + mConnection.getCodePointBeforeCursor(); + if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) { + final int lengthToDeleteAgain = Character.isSupplementaryCodePoint( + codePointBeforeCursorToDeleteAgain) ? 2 : 1; + mConnection.deleteSurroundingText(lengthToDeleteAgain, 0); + if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { + ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain, + true /* shouldUncommitLogUnit */); + } } } } @@ -2335,11 +2372,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing if (currentSettings.mCorrectionEnabled) { final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR - : new String(new int[] { primaryCode }, 0, 1); + : StringUtils.newSingleCodePointString(primaryCode); commitCurrentAutoCorrection(separator); didAutoCorrect = true; } else { - commitTyped(new String(new int[]{primaryCode}, 0, 1)); + commitTyped(StringUtils.newSingleCodePointString(primaryCode)); } } @@ -2488,7 +2525,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>(); getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_TYPING, - new OnGetSuggestedWordsCallback() { + SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords(final SuggestedWords suggestedWords) { holder.set(suggestedWords); @@ -2503,7 +2540,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void getSuggestedWords(final int sessionId, + private void getSuggestedWords(final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); final Suggest suggest = mSuggest; @@ -2528,18 +2565,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled, - additionalFeaturesOptions, sessionId, callback); + additionalFeaturesOptions, sessionId, sequenceNumber, callback); } private void getSuggestedWordsOrOlderSuggestionsAsync(final int sessionId, - final OnGetSuggestedWordsCallback callback) { - mInputUpdater.getSuggestedWords(sessionId, new OnGetSuggestedWordsCallback() { - @Override - public void onGetSuggestedWords(SuggestedWords suggestedWords) { - callback.onGetSuggestedWords(maybeRetrieveOlderSuggestions( - mWordComposer.getTypedWord(), suggestedWords)); - } - }); + final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { + mInputUpdater.getSuggestedWords(sessionId, sequenceNumber, + new OnGetSuggestedWordsCallback() { + @Override + public void onGetSuggestedWords(SuggestedWords suggestedWords) { + callback.onGetSuggestedWords(maybeRetrieveOlderSuggestions( + mWordComposer.getTypedWord(), suggestedWords)); + } + }); } private SuggestedWords maybeRetrieveOlderSuggestions(final String typedWord, @@ -2874,30 +2912,30 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // We come here if there weren't any suggestion spans on this word. We will try to // compute suggestions for it instead. mInputUpdater.getSuggestedWords(Suggest.SESSION_TYPING, - new OnGetSuggestedWordsCallback() { - @Override - public void onGetSuggestedWords( - final SuggestedWords suggestedWordsIncludingTypedWord) { - final SuggestedWords suggestedWords; - if (suggestedWordsIncludingTypedWord.size() > 1) { - // We were able to compute new suggestions for this word. - // Remove the typed word, since we don't want to display it in this case. - // The #getSuggestedWordsExcludingTypedWord() method sets willAutoCorrect to - // false. - suggestedWords = suggestedWordsIncludingTypedWord - .getSuggestedWordsExcludingTypedWord(); - } else { - // No saved suggestions, and we were unable to compute any good one either. - // Rather than displaying an empty suggestion strip, we'll display the - // original word alone in the middle. - // Since there is only one word, willAutoCorrect is false. - suggestedWords = suggestedWordsIncludingTypedWord; - } - // We need to pass typedWord because mWordComposer.mTypedWord may differ from - // typedWord. - unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords, - typedWord); - }}); + SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { + @Override + public void onGetSuggestedWords( + final SuggestedWords suggestedWordsIncludingTypedWord) { + final SuggestedWords suggestedWords; + if (suggestedWordsIncludingTypedWord.size() > 1) { + // We were able to compute new suggestions for this word. + // Remove the typed word, since we don't want to display it in this + // case. The #getSuggestedWordsExcludingTypedWord() method sets + // willAutoCorrect to false. + suggestedWords = suggestedWordsIncludingTypedWord + .getSuggestedWordsExcludingTypedWord(); + } else { + // No saved suggestions, and we were unable to compute any good one + // either. Rather than displaying an empty suggestion strip, we'll + // display the original word alone in the middle. + // Since there is only one word, willAutoCorrect is false. + suggestedWords = suggestedWordsIncludingTypedWord; + } + // We need to pass typedWord because mWordComposer.mTypedWord may + // differ from typedWord. + unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip( + suggestedWords, typedWord); + }}); } else { // We found suggestion spans in the word. We'll create the SuggestedWords out of // them, and make willAutoCorrect false. @@ -2978,8 +3016,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final String originallyTypedWord = mLastComposedWord.mTypedWord; final String committedWord = mLastComposedWord.mCommittedWord; final int cancelLength = committedWord.length(); - final int separatorLength = LastComposedWord.getSeparatorLength( - mLastComposedWord.mSeparatorString); + // We want java chars, not codepoints for the following. + final int separatorLength = mLastComposedWord.mSeparatorString.length(); // TODO: should we check our saved separator against the actual contents of the text view? final int deleteLength = cancelLength + separatorLength; if (DEBUG) { diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index e43cab5ca..c212f9c81 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -294,7 +294,14 @@ public final class RichInputConnection { // the text field, then we can return the cached version right away. if (cachedLength >= n || cachedLength >= mExpectedCursorPosition) { final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText); - s.append(mComposingText); + // We call #toString() here to create a temporary object. + // In some situations, this method is called on a worker thread, and it's possible + // the main thread touches the contents of mComposingText while this worker thread + // is suspended, because mComposingText is a StringBuilder. This may lead to crashes, + // so we call #toString() on it. That will result in the return value being strictly + // speaking wrong, but since this is used for basing bigram probability off, and + // it's only going to matter for one getSuggestions call, it's fine in the practice. + s.append(mComposingText.toString()); if (s.length() > n) { s.delete(0, s.length() - n); } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index c270d47d0..0a4c7a55d 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -217,15 +217,17 @@ public final class Suggest { public void getSuggestedWords(final WordComposer wordComposer, final String prevWordForBigram, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final boolean isCorrectionEnabled, - final int[] additionalFeaturesOptions, final int sessionId, + final int[] additionalFeaturesOptions, final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { LatinImeLogger.onStartSuggestion(prevWordForBigram); if (wordComposer.isBatchMode()) { getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo, - blockOffensiveWords, additionalFeaturesOptions, sessionId, callback); + blockOffensiveWords, additionalFeaturesOptions, sessionId, sequenceNumber, + callback); } else { getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo, - blockOffensiveWords, isCorrectionEnabled, additionalFeaturesOptions, callback); + blockOffensiveWords, isCorrectionEnabled, additionalFeaturesOptions, + sequenceNumber, callback); } } @@ -234,7 +236,8 @@ public final class Suggest { private void getSuggestedWordsForTypingInput(final WordComposer wordComposer, final String prevWordForBigram, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final boolean isCorrectionEnabled, - final int[] additionalFeaturesOptions, final OnGetSuggestedWordsCallback callback) { + final int[] additionalFeaturesOptions, final int sequenceNumber, + final OnGetSuggestedWordsCallback callback) { final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS); @@ -347,7 +350,7 @@ public final class Suggest { hasAutoCorrection, /* willAutoCorrect */ false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, - !wordComposer.isComposingWord() /* isPrediction */)); + !wordComposer.isComposingWord() /* isPrediction */, sequenceNumber)); } // Retrieves suggestions for the batch input @@ -355,7 +358,8 @@ public final class Suggest { private void getSuggestedWordsForBatchInput(final WordComposer wordComposer, final String prevWordForBigram, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, - final int sessionId, final OnGetSuggestedWordsCallback callback) { + final int sessionId, final int sequenceNumber, + final OnGetSuggestedWordsCallback callback) { final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS); @@ -408,7 +412,7 @@ public final class Suggest { false /* willAutoCorrect */, false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, - false /* isPrediction */)); + false /* isPrediction */, sequenceNumber)); } private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo( @@ -475,7 +479,7 @@ public final class Suggest { } return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind, wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord, - SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */); + wordInfo.mAutoCommitFirstWordConfidence); } public void close() { diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index fed4cdbbb..97c89dd4e 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -29,6 +29,7 @@ import java.util.HashSet; public final class SuggestedWords { public static final int INDEX_OF_TYPED_WORD = 0; public static final int INDEX_OF_AUTO_CORRECTION = 1; + public static final int NOT_A_SEQUENCE_NUMBER = -1; private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = CollectionUtils.newArrayList(0); @@ -43,6 +44,7 @@ public final class SuggestedWords { public final boolean mIsPunctuationSuggestions; public final boolean mIsObsoleteSuggestions; public final boolean mIsPrediction; + public final int mSequenceNumber; // Sequence number for auto-commit. private final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList; public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, @@ -51,12 +53,24 @@ public final class SuggestedWords { final boolean isPunctuationSuggestions, final boolean isObsoleteSuggestions, final boolean isPrediction) { + this(suggestedWordInfoList, typedWordValid, willAutoCorrect, isPunctuationSuggestions, + isObsoleteSuggestions, isPrediction, NOT_A_SEQUENCE_NUMBER); + } + + public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, + final boolean typedWordValid, + final boolean willAutoCorrect, + final boolean isPunctuationSuggestions, + final boolean isObsoleteSuggestions, + final boolean isPrediction, + final int sequenceNumber) { mSuggestedWordInfoList = suggestedWordInfoList; mTypedWordValid = typedWordValid; mWillAutoCorrect = willAutoCorrect; mIsPunctuationSuggestions = isPunctuationSuggestions; mIsObsoleteSuggestions = isObsoleteSuggestions; mIsPrediction = isPrediction; + mSequenceNumber = sequenceNumber; } public boolean isEmpty() { diff --git a/java/src/com/android/inputmethod/latin/about/AboutPreferences.java b/java/src/com/android/inputmethod/latin/about/AboutPreferences.java new file mode 100644 index 000000000..f60b189f1 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/about/AboutPreferences.java @@ -0,0 +1,28 @@ +/* + * 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.about; + +import android.app.Fragment; + +/** + * Dummy class of AboutPreferences. Never use this. + */ +public final class AboutPreferences extends Fragment { + private AboutPreferences() { + // Prevents this from being instantiated + } +} diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java index a1e36006b..1de15a333 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java +++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java @@ -114,15 +114,6 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB } /** - * Return whether the passed charsequence is in the dictionary. - */ - @Override - public boolean isValidWord(final String word) { - // Words included only in the user history should be treated as not in dictionary words. - return false; - } - - /** * Pair will be added to the decaying dictionary. * * The first word may be null. That means we don't know the context, in other words, diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java index ef6ab2a38..a23e37795 100644 --- a/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java +++ b/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java @@ -21,6 +21,7 @@ import android.os.Bundle; import android.preference.PreferenceActivity; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.FragmentUtils; public final class DebugSettingsActivity extends PreferenceActivity { private static final String DEFAULT_FRAGMENT = DebugSettings.class.getName(); @@ -42,6 +43,6 @@ public final class DebugSettingsActivity extends PreferenceActivity { // TODO: Uncomment the override annotation once we start using SDK version 19. // @Override public boolean isValidFragment(String fragmentName) { - return fragmentName.equals(DEFAULT_FRAGMENT); + return FragmentUtils.isValidFragment(fragmentName); } } diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index 1a0fecc62..dc005bbdf 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -101,6 +101,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang // Emoji public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys"; public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id"; + public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id"; private Resources mRes; private SharedPreferences mPrefs; @@ -383,15 +384,25 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang return prefs.getString(PREF_EMOJI_RECENT_KEYS, ""); } - public static void writeEmojiCategoryLastTypedId( - final SharedPreferences prefs, final int category, final int id) { - final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category; - prefs.edit().putInt(key, id).apply(); + public static void writeLastTypedEmojiCategoryPageId( + final SharedPreferences prefs, final int categoryId, final int categoryPageId) { + final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId; + prefs.edit().putInt(key, categoryPageId).apply(); } - public static int readEmojiCategoryLastTypedId( - final SharedPreferences prefs, final int category) { - final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category; + public static int readLastTypedEmojiCategoryPageId( + final SharedPreferences prefs, final int categoryId) { + final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId; return prefs.getInt(key, 0); } + + public static void writeLastShownEmojiCategoryId( + final SharedPreferences prefs, final int categoryId) { + prefs.edit().putInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, categoryId).apply(); + } + + public static int readLastShownEmojiCategoryId( + final SharedPreferences prefs, final int defValue) { + return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, defValue); + } } diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java index ad68f8c37..c899507e3 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin.settings; +import com.android.inputmethod.latin.utils.FragmentUtils; + import android.content.Intent; import android.preference.PreferenceActivity; @@ -36,6 +38,6 @@ public final class SettingsActivity extends PreferenceActivity { // TODO: Uncomment the override annotation once we start using SDK version 19. // @Override public boolean isValidFragment(String fragmentName) { - return fragmentName.equals(DEFAULT_FRAGMENT); + return FragmentUtils.isValidFragment(fragmentName); } } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java index aba563746..df9a76119 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin.spellcheck; +import com.android.inputmethod.latin.utils.FragmentUtils; + import android.content.Intent; import android.os.Bundle; import android.preference.PreferenceActivity; @@ -42,6 +44,6 @@ public final class SpellCheckerSettingsActivity extends PreferenceActivity { // TODO: Uncomment the override annotation once we start using SDK version 19. // @Override public boolean isValidFragment(String fragmentName) { - return fragmentName.equals(DEFAULT_FRAGMENT); + return FragmentUtils.isValidFragment(fragmentName); } } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index 8d2689a7d..faa5560e4 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -302,18 +302,19 @@ final class SuggestionStripLayoutHelper { final int countInStrip = mSuggestionsCountInStrip; setupWordViewsTextAndColor(suggestedWords, countInStrip); final TextView centerWordView = mWordViews.get(mCenterPositionInStrip); - final int stripWidth = placerView.getWidth(); - final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth); + final int availableStripWidth = placerView.getWidth() + - placerView.getPaddingRight() - placerView.getPaddingLeft(); + final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, availableStripWidth); if (getTextScaleX(centerWordView.getText(), centerWidth, centerWordView.getPaint()) < MIN_TEXT_XSCALE) { // Layout only the most relevant suggested word at the center of the suggestion strip // by consolidating all slots in the strip. mMoreSuggestionsAvailable = (suggestedWords.size() > 1); - layoutWord(mCenterPositionInStrip, stripWidth); + layoutWord(mCenterPositionInStrip, availableStripWidth - mPadding); stripView.addView(centerWordView); setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT); if (SuggestionStripView.DBG) { - layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth); + layoutDebugInfo(mCenterPositionInStrip, placerView, availableStripWidth); } return; } @@ -328,7 +329,7 @@ final class SuggestionStripLayoutHelper { x += divider.getMeasuredWidth(); } - final int width = getSuggestionWidth(positionInStrip, stripWidth); + final int width = getSuggestionWidth(positionInStrip, availableStripWidth); final TextView wordView = layoutWord(positionInStrip, width); stripView.addView(wordView); setLayoutWeight(wordView, getSuggestionWeight(positionInStrip), @@ -373,9 +374,9 @@ final class SuggestionStripLayoutHelper { // Disable this suggestion if the suggestion is null or empty. wordView.setEnabled(!TextUtils.isEmpty(word)); final CharSequence text = getEllipsizedText(word, width, wordView.getPaint()); - final float scaleX = wordView.getTextScaleX(); + final float scaleX = getTextScaleX(word, width, wordView.getPaint()); wordView.setText(text); // TextView.setText() resets text scale x to 1.0. - wordView.setTextScaleX(scaleX); + wordView.setTextScaleX(Math.max(scaleX, MIN_TEXT_XSCALE)); return wordView; } @@ -545,8 +546,24 @@ final class SuggestionStripLayoutHelper { // 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 CharSequence ellipsized = TextUtils.ellipsize( - text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE); + 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); + } paint.setTextScaleX(MIN_TEXT_XSCALE); return ellipsized; } diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java new file mode 100644 index 000000000..ee2b97b2a --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java @@ -0,0 +1,52 @@ +/* + * 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.dictionarypack.DictionarySettingsFragment; +import com.android.inputmethod.latin.about.AboutPreferences; +import com.android.inputmethod.latin.settings.AdditionalSubtypeSettings; +import com.android.inputmethod.latin.settings.DebugSettings; +import com.android.inputmethod.latin.settings.SettingsFragment; +import com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment; +import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragment; +import com.android.inputmethod.latin.userdictionary.UserDictionaryList; +import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker; +import com.android.inputmethod.latin.userdictionary.UserDictionarySettings; +import com.android.inputmethod.research.FeedbackFragment; + +import java.util.HashSet; + +public class FragmentUtils { + private static final HashSet<String> sLatinImeFragments = new HashSet<String>(); + static { + sLatinImeFragments.add(DictionarySettingsFragment.class.getName()); + sLatinImeFragments.add(AboutPreferences.class.getName()); + sLatinImeFragments.add(AdditionalSubtypeSettings.class.getName()); + sLatinImeFragments.add(DebugSettings.class.getName()); + sLatinImeFragments.add(SettingsFragment.class.getName()); + sLatinImeFragments.add(SpellCheckerSettingsFragment.class.getName()); + sLatinImeFragments.add(UserDictionaryAddWordFragment.class.getName()); + sLatinImeFragments.add(UserDictionaryList.class.getName()); + sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName()); + sLatinImeFragments.add(UserDictionarySettings.class.getName()); + sLatinImeFragments.add(FeedbackFragment.class.getName()); + } + + public static boolean isValidFragment(String fragmentName) { + return sLatinImeFragments.contains(fragmentName); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java index 121aecf0f..a36548392 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -48,6 +48,16 @@ public final class StringUtils { return text.codePointCount(0, text.length()); } + public static String newSingleCodePointString(int codePoint) { + if (Character.charCount(codePoint) == 1) { + // Optimization: avoid creating an temporary array for characters that are + // represented by a single char value + return String.valueOf((char) codePoint); + } + // For surrogate pair + return new String(Character.toChars(codePoint)); + } + public static boolean containsInArray(final String text, final String[] array) { for (final String element : array) { if (text.equals(element)) return true; |