diff options
Diffstat (limited to 'java/src')
13 files changed, 544 insertions, 371 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java index ba08c593c..70e38fdb0 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java @@ -275,17 +275,6 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat return false; } - @Override - public AccessibilityNodeInfoCompat findAccessibilityFocus(int virtualViewId) { - return createAccessibilityNodeInfo(mAccessibilityFocusedView); - } - - @Override - public AccessibilityNodeInfoCompat accessibilityFocusSearch(int direction, int virtualViewId) { - // Focus search is not currently supported for IMEs. - return null; - } - /** * Sends an accessibility event for the given {@link Key}. * diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java index b4fa86dd5..be7644fb5 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java @@ -159,7 +159,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel @Override public boolean dismissMoreKeysPanel() { - if (mIsDismissing) return false; + if (mIsDismissing || mController == null) return false; mIsDismissing = true; final boolean dismissed = mController.dismissMoreKeysPanel(); mIsDismissing = false; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 14d61e228..8c218c6d3 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -428,23 +428,23 @@ public final class KeyboardTextsSet { // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK /* 54 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB", /* 55 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB", - // U+064F: "ُ" ARABIC DAMMA - // U+064C: "ٌ" ARABIC DAMMATAN - // U+0651: "ّ" ARABIC SHADDA + // U+0655: "ٕ" ARABIC HAMZA BELOW + // U+0654: "ٔ" ARABIC HAMZA ABOVE // U+0652: "ْ" ARABIC SUKUN - // U+0653: "ٓ" ARABIC MADDAH ABOVE // U+064D: "ٍ" ARABIC KASRATAN + // U+064C: "ٌ" ARABIC DAMMATAN // U+064B: "ً" ARABIC FATHATAN + // U+0651: "ّ" ARABIC SHADDA + // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF + // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF + // U+0653: "ٓ" ARABIC MADDAH ABOVE // U+0650: "ِ" ARABIC KASRA + // U+064F: "ُ" ARABIC DAMMA // U+064E: "َ" ARABIC FATHA // U+0640: "ـ" ARABIC TATWEEL - // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF - // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF - // U+0655: "ٕ" ARABIC HAMZA BELOW - // U+0654: "ٔ" ARABIC HAMZA ABOVE // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. - /* 56 */ "!fixedColumnOrder!5,\u064F,\u064C,\u0651,\u0652,\u0653,\u064D,\u064B,\u0650,\u064E,\u0640\u0640\u0640|\u0640,\u0656,\u0670,\u0655,\u0654", - /* 57 */ "\u064F", + /* 56 */ "!fixedColumnOrder!7,\u0655,\u0654,\u0652,\u064D,\u064C,\u064B,\u0651,\u0656,\u0670,\u0653,\u0650,\u064F,\u064E,\u0640\u0640\u0640|\u0640", + /* 57 */ "\u0651", // U+0661: "١" ARABIC-INDIC DIGIT ONE /* 58 */ "\u0661", // U+0662: "٢" ARABIC-INDIC DIGIT TWO @@ -993,7 +993,7 @@ public final class KeyboardTextsSet { /* ~41 */ // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_double_quote">“,”,„,‟,«|»,»|«</string> - /* 42 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB", + /* 42 */ "!fixedColumnOrder!4,\u201C,\u201D,\",\'", // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> /* 43 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", @@ -1031,25 +1031,25 @@ public final class KeyboardTextsSet { // U+201D: "”" RIGHT DOUBLE QUOTATION MARK // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK - /* 54 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB", - /* 55 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB", - // U+0651: "ّ" ARABIC SHADDA - // U+064F: "ُ" ARABIC DAMMA - // U+0650: "ِ" ARABIC KASRA - // U+064E: "َ" ARABIC FATHA - // U+0653: "ٓ" ARABIC MADDAH ABOVE + /* 54 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>", + /* 55 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<", // U+0655: "ٕ" ARABIC HAMZA BELOW - // U+0654: "ٔ" ARABIC HAMZA ABOVE - // U+064D: "ٍ" ARABIC KASRATAN - // U+064B: "ً" ARABIC FATHATAN // U+0652: "ْ" ARABIC SUKUN - // U+0640: "ـ" ARABIC TATWEEL + // U+0651: "ّ" ARABIC SHADDA // U+064C: "ٌ" ARABIC DAMMATAN + // U+064D: "ٍ" ARABIC KASRATAN + // U+064B: "ً" ARABIC FATHATAN + // U+0654: "ٔ" ARABIC HAMZA ABOVE // U+0656: "ٖ" ARABIC SUBSCRIPT ALEF // U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF + // U+0653: "ٓ" ARABIC MADDAH ABOVE + // U+064F: "ُ" ARABIC DAMMA + // U+0650: "ِ" ARABIC KASRA + // U+064E: "َ" ARABIC FATHA + // U+0640: "ـ" ARABIC TATWEEL // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. - /* 56 */ "!fixedColumnOrder!5,\u0651,\u064F,\u0650,\u064E,\u0653,\u0655,\u0654,\u064D,\u064B,\u0652,\u0640\u0640\u0640|\u0640,\u064C,\u0656,\u0670", - /* 57 */ "\u0653", + /* 56 */ "!fixedColumnOrder!7,\u0655,\u0652,\u0651,\u064C,\u064D,\u064B,\u0654,\u0656,\u0670,\u0653,\u064F,\u0650,\u064E,\u0640\u0640\u0640|\u0640", + /* 57 */ "\u064B", // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE /* 58 */ "\u06F1", // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO @@ -1101,14 +1101,16 @@ public final class KeyboardTextsSet { // U+060C: "،" ARABIC COMMA // U+061B: "؛" ARABIC SEMICOLON // U+061F: "؟" ARABIC QUESTION MARK + // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK /* 98 */ "\u060C", /* 99 */ "!", /* 100 */ "!,\\,", /* 101 */ "\u061F", /* 102 */ "\u061F,?", - /* 103 */ null, - /* 104 */ null, - /* 105 */ "\u061F,\u061B,!,:,-,/,\',\"", + /* 103 */ "\u060C", + /* 104 */ "\u061F", + /* 105 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB", }; /* Language fi: Finnish */ diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 7cd9bc2a8..9c3d46e70 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -115,4 +115,12 @@ public abstract class Dictionary { public void close() { // empty base implementation } + + /** + * Subclasses may override to indicate that this Dictionary is not yet properly initialized. + */ + + public boolean isInitialized() { + return true; + } } diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index 1a05fcd86..26c2e637e 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -82,8 +82,9 @@ public class DictionaryCollection extends Dictionary { return maxFreq; } - public boolean isEmpty() { - return mDictionaries.isEmpty(); + @Override + public boolean isInitialized() { + return !mDictionaries.isEmpty(); } @Override diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 8c244d686..8a5fc495e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -122,7 +122,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private int mSpaceState; private SettingsValues mCurrentSettings; - private InputAttributes mInputAttributes; private View mExtractArea; private View mKeyPreviewBackingView; @@ -428,10 +427,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged() // is not guaranteed. It may even be called at the same time on a different thread. if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this); + final InputAttributes inputAttributes = + new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode()); final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() { @Override protected SettingsValues job(Resources res) { - return new SettingsValues(mPrefs, mInputAttributes, LatinIME.this); + return new SettingsValues(mPrefs, inputAttributes, LatinIME.this); } }; mCurrentSettings = job.runInLocale(mResources, mSubtypeSwitcher.getCurrentSubtypeLocale()); @@ -451,11 +452,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen oldContactsDictionary = null; } mSuggest = new Suggest(this, subtypeLocale); - if (mCurrentSettings.isCorrectionOn()) { + if (mCurrentSettings.mCorrectionEnabled) { mSuggest.setAutoCorrectionThreshold(mCurrentSettings.mAutoCorrectionThreshold); } mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.getInstance().initSuggest(mSuggest); + } mUserDictionary = new UserBinaryDictionary(this, localeStr); mIsUserDictionaryAvailable = mUserDictionary.isEnabled(); @@ -671,7 +675,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen updateFullscreenMode(); mLastSelectionStart = editorInfo.initialSelStart; mLastSelectionEnd = editorInfo.initialSelEnd; - mInputAttributes = new InputAttributes(editorInfo, isFullscreenMode()); mApplicationSpecifiedCompletions = null; inputView.closing(); @@ -682,7 +685,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen loadSettings(); - if (mSuggest != null && mCurrentSettings.isCorrectionOn()) { + if (mSuggest != null && mCurrentSettings.mCorrectionEnabled) { mSuggest.setAutoCorrectionThreshold(mCurrentSettings.mAutoCorrectionThreshold); } @@ -825,7 +828,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen */ @Override public void onExtractedTextClicked() { - if (isSuggestionsRequested()) return; + if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) return; super.onExtractedTextClicked(); } @@ -841,7 +844,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen */ @Override public void onExtractedCursorMovement(int dx, int dy) { - if (isSuggestionsRequested()) return; + if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) return; super.onExtractedCursorMovement(dx, dy); } @@ -872,33 +875,32 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions); } - if (mInputAttributes.mApplicationSpecifiedCompletionOn) { - mApplicationSpecifiedCompletions = applicationSpecifiedCompletions; - if (applicationSpecifiedCompletions == null) { - clearSuggestions(); - return; - } - - final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords = - SuggestedWords.getFromApplicationSpecifiedCompletions( - applicationSpecifiedCompletions); - final SuggestedWords suggestedWords = new SuggestedWords( - applicationSuggestedWords, - false /* typedWordValid */, - false /* hasAutoCorrectionCandidate */, - false /* allowsToBeAutoCorrected */, - false /* isPunctuationSuggestions */, - false /* isObsoleteSuggestions */, - false /* isPrediction */); - // When in fullscreen mode, show completions generated by the application - final boolean isAutoCorrection = false; - setSuggestions(suggestedWords, isAutoCorrection); - setAutoCorrectionIndicator(isAutoCorrection); - // TODO: is this the right thing to do? What should we auto-correct to in - // this case? This says to keep whatever the user typed. - mWordComposer.setAutoCorrection(mWordComposer.getTypedWord()); - setSuggestionStripShown(true); + if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return; + mApplicationSpecifiedCompletions = applicationSpecifiedCompletions; + if (applicationSpecifiedCompletions == null) { + clearSuggestions(); + return; } + + final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords = + SuggestedWords.getFromApplicationSpecifiedCompletions( + applicationSpecifiedCompletions); + final SuggestedWords suggestedWords = new SuggestedWords( + applicationSuggestedWords, + false /* typedWordValid */, + false /* hasAutoCorrectionCandidate */, + false /* allowsToBeAutoCorrected */, + false /* isPunctuationSuggestions */, + false /* isObsoleteSuggestions */, + false /* isPrediction */); + // When in fullscreen mode, show completions generated by the application + final boolean isAutoCorrection = false; + setSuggestions(suggestedWords, isAutoCorrection); + setAutoCorrectionIndicator(isAutoCorrection); + // TODO: is this the right thing to do? What should we auto-correct to in + // this case? This says to keep whatever the user typed. + mWordComposer.setAutoCorrection(mWordComposer.getTypedWord()); + setSuggestionStripShown(true); } private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) { @@ -1000,8 +1002,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // This will reset the whole input state to the starting state. It will clear - // the composing word, reset the last composed word, tell the inputconnection - // and the composingStateManager about it. + // the composing word, reset the last composed word, tell the inputconnection about it. private void resetEntireInputState() { resetComposingState(true /* alsoResetLastComposedWord */); updateSuggestions(); @@ -1074,7 +1075,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private boolean maybeDoubleSpace() { - if (mCurrentSettings.mCorrectionMode == Suggest.CORRECTION_NONE) return false; + if (!mCurrentSettings.mCorrectionEnabled) return false; if (!mHandler.isAcceptingDoubleSpaces()) return false; final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0); if (lastThree != null && lastThree.length() == 3 @@ -1277,8 +1278,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } break; default: - if (primaryCode == Keyboard.CODE_TAB - && mInputAttributes.mEditorAction == EditorInfo.IME_ACTION_NEXT) { + if (primaryCode == Keyboard.CODE_TAB && mCurrentSettings.isEditorActionNext()) { performEditorAction(EditorInfo.IME_ACTION_NEXT); break; } @@ -1448,7 +1448,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } } - if (isSuggestionsRequested()) { + if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) { restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(); } } @@ -1493,7 +1493,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // thread here. if (!isComposingWord && (isAlphabet(primaryCode) || mCurrentSettings.isSymbolExcludedFromWordSeparators(primaryCode)) - && isSuggestionsRequested() && + && mCurrentSettings.isSuggestionsRequested(mDisplayOrientation) && !mConnection.isCursorTouchingWord(mCurrentSettings)) { // Reset entirely the composing state anyway, then start composing a new word unless // the character is a single quote. The idea here is, single quote is not a @@ -1556,8 +1556,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // not to auto correct, but accept the typed word. For instance, // in Italian dov' should not be expanded to dove' because the elision // requires the last vowel to be removed. - final boolean shouldAutoCorrect = mCurrentSettings.isCorrectionOn(); - if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) { + if (mCurrentSettings.mCorrectionEnabled && primaryCode != Keyboard.CODE_SINGLE_QUOTE) { commitCurrentAutoCorrection(primaryCode); didAutoCorrect = true; } else { @@ -1575,7 +1574,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen sendKeyCodePoint(primaryCode); if (Keyboard.CODE_SPACE == primaryCode) { - if (isSuggestionsRequested()) { + if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) { if (maybeDoubleSpace()) { mSpaceState = SPACE_STATE_DOUBLE; } else if (!isShowingPunctuationList()) { @@ -1626,31 +1625,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen inputView.closing(); } - public boolean isSuggestionsRequested() { - // TODO: move this method to mSettingsValues - return mInputAttributes.mIsSettingsSuggestionStripOn - && (mCurrentSettings.isCorrectionOn() || isShowingSuggestionsStrip()); - } - public boolean isShowingPunctuationList() { if (mSuggestionsView == null) return false; return mCurrentSettings.mSuggestPuncList == mSuggestionsView.getSuggestions(); } - public boolean isShowingSuggestionsStrip() { - return mCurrentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation); - } - public boolean isSuggestionsStripVisible() { if (mSuggestionsView == null) return false; if (mSuggestionsView.isShowingAddToDictionaryHint()) return true; - if (!isShowingSuggestionsStrip()) + if (!mCurrentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation)) return false; - if (mInputAttributes.mApplicationSpecifiedCompletionOn) + if (mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return true; - return isSuggestionsRequested(); + return mCurrentSettings.isSuggestionsRequested(mDisplayOrientation); } public void switchToKeyboardView() { @@ -1699,7 +1688,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void updateSuggestions() { // Check if we have a suggestion engine attached. - if ((mSuggest == null || !isSuggestionsRequested())) { + if ((mSuggest == null || !mCurrentSettings.isSuggestionsRequested(mDisplayOrientation))) { if (mWordComposer.isComposingWord()) { Log.w(TAG, "Called updateSuggestions but suggestions were not requested!"); mWordComposer.setAutoCorrection(mWordComposer.getTypedWord()); @@ -1721,7 +1710,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // getSuggestedWords handles gracefully a null value of prevWord final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer, prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), - mCurrentSettings.mCorrectionMode); + mCurrentSettings.mCorrectionEnabled); // Basically, we update the suggestion strip only when suggestion count > 1. However, // there is an exception: We update the suggestion strip whenever typed word's length @@ -1830,7 +1819,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - if (mInputAttributes.mApplicationSpecifiedCompletionOn + if (mCurrentSettings.isApplicationSpecifiedCompletionsOn() && mApplicationSpecifiedCompletions != null && index >= 0 && index < mApplicationSpecifiedCompletions.length) { if (mSuggestionsView != null) { @@ -1873,8 +1862,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // - There is a dictionary and the word is not in it // Please note that if mSuggest is null, it means that everything is off: suggestion // and correction, so we shouldn't try to show the hint - // We used to look at mCorrectionMode here, but showing the hint should have nothing - // to do with the autocorrection setting. final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null // If there is no dictionary the hint should be shown. && (!mSuggest.hasMainDictionary() @@ -1906,14 +1893,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen */ private void commitChosenWord(final CharSequence chosenWord, final int commitType, final int separatorCode) { - if (mCurrentSettings.mEnableSuggestionSpanInsertion) { - final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions(); - mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( - this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), - 1); - } else { - mConnection.commitText(chosenWord, 1); - } + final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions(); + mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( + this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1); if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.latinIME_commitText(chosenWord); } @@ -1928,7 +1910,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void updateBigramPredictions() { - if (mSuggest == null || !isSuggestionsRequested()) + if (mSuggest == null || !mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) return; if (!mCurrentSettings.mBigramPredictionEnabled) { @@ -1937,7 +1919,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } final SuggestedWords suggestedWords; - if (mCurrentSettings.mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { + if (mCurrentSettings.mCorrectionEnabled) { final CharSequence prevWord = mConnection.getThisWord(mCurrentSettings.mWordSeparators); if (!TextUtils.isEmpty(prevWord)) { suggestedWords = mSuggest.getBigramPredictions(prevWord); @@ -1953,12 +1935,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // showSuggestions will retrieve the word near the cursor, we don't want that here) showSuggestions(suggestedWords, ""); } else { - if (!isShowingPunctuationList()) setPunctuationSuggestions(); + clearSuggestions(); } } public void setPunctuationSuggestions() { - setSuggestions(mCurrentSettings.mSuggestPuncList, false); + if (mCurrentSettings.mBigramPredictionEnabled) { + clearSuggestions(); + } else { + setSuggestions(mCurrentSettings.mSuggestPuncList, false); + } setAutoCorrectionIndicator(false); setSuggestionStripShown(isSuggestionsStripVisible()); } @@ -1966,10 +1952,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) { if (TextUtils.isEmpty(suggestion)) return null; - // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be - // adding words in situations where the user or application really didn't - // want corrections enabled or learned. - if (!mCurrentSettings.isCorrectionOn()) return null; + // If correction is not enabled, we don't add words to the user history dictionary. + // That's to avoid unintended additions in some sensitive fields, or fields that + // expect to receive non-words. + if (!mCurrentSettings.mCorrectionEnabled) return null; final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary; if (userHistoryDictionary != null) { @@ -2224,13 +2210,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1; p.println(" Keyboard mode = " + keyboardMode); - p.println(" mIsSuggestionsRequested=" + mInputAttributes.mIsSettingsSuggestionStripOn); - p.println(" mCorrectionMode=" + mCurrentSettings.mCorrectionMode); + p.println(" mIsSuggestionsSuggestionsRequested = " + + mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)); + p.println(" mCorrectionEnabled=" + mCurrentSettings.mCorrectionEnabled); p.println(" isComposingWord=" + mWordComposer.isComposingWord()); - p.println(" isCorrectionOn=" + mCurrentSettings.isCorrectionOn()); p.println(" mSoundOn=" + mCurrentSettings.mSoundOn); p.println(" mVibrateOn=" + mCurrentSettings.mVibrateOn); p.println(" mKeyPreviewPopupOn=" + mCurrentSettings.mKeyPreviewPopupOn); - p.println(" mInputAttributes=" + mInputAttributes.toString()); + p.println(" inputAttributes=" + mCurrentSettings.getInputAttributesDebugString()); } } diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java index 46efa78f1..cf3cc7873 100644 --- a/java/src/com/android/inputmethod/latin/ResearchLogger.java +++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java @@ -51,7 +51,9 @@ import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; @@ -67,6 +69,7 @@ import java.util.UUID; public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = ResearchLogger.class.getSimpleName(); private static final boolean DEBUG = false; + private static final boolean OUTPUT_ENTIRE_BUFFER = false; // true may disclose private info /* package */ static boolean sIsLogging = false; private static final int OUTPUT_FORMAT_VERSION = 1; private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; @@ -94,11 +97,21 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private static final int LOGGING_STATE_OFF = 0; private static final int LOGGING_STATE_ON = 1; private static final int LOGGING_STATE_STOPPING = 2; + private boolean mIsPasswordView = false; + // digits entered by the user are replaced with this codepoint. + /* package for test */ static final int DIGIT_REPLACEMENT_CODEPOINT = + Character.codePointAt("\uE000", 0); // U+E000 is in the "private-use area" + // U+E001 is in the "private-use area" + /* package for test */ static final String WORD_REPLACEMENT_STRING = "\uE001"; // set when LatinIME should ignore an onUpdateSelection() callback that // arises from operations in this class private static boolean sLatinIMEExpectingUpdateSelection = false; + // used to check whether words are not unique + private Suggest mSuggest; + private Dictionary mDictionary; + private static class NullOutputStream extends OutputStream { /** {@inheritDoc} */ @Override @@ -313,130 +326,272 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang latinIME.showOptionDialog(builder.create()); } + public void initSuggest(Suggest suggest) { + mSuggest = suggest; + } + + private void setIsPasswordView(boolean isPasswordView) { + mIsPasswordView = isPasswordView; + } + + private boolean isAllowedToLog() { + return mLoggingState == LOGGING_STATE_ON && !mIsPasswordView; + } + private static final String CURRENT_TIME_KEY = "_ct"; private static final String UPTIME_KEY = "_ut"; private static final String EVENT_TYPE_KEY = "_ty"; private static final Object[] EVENTKEYS_NULLVALUES = {}; + private LogUnit mCurrentLogUnit = new LogUnit(); + /** - * Write a description of the event out to the ResearchLog. + * Buffer a research log event, flagging it as privacy-sensitive. * - * Runs in the background to avoid blocking the UI thread. + * This event contains potentially private information. If the word that this event is a part + * of is determined to be privacy-sensitive, then this event should not be included in the + * output log. The system waits to output until the containing word is known. * * @param keys an array containing a descriptive name for the event, followed by the keys * @param values an array of values, either a String or Number. length should be one * less than the keys array */ - private synchronized void writeEvent(final String[] keys, final Object[] values) { + private synchronized void enqueuePotentiallyPrivateEvent(final String[] keys, + final Object[] values) { assert values.length + 1 == keys.length; - if (mLoggingState == LOGGING_STATE_ON) { - mLoggingHandler.post(new Runnable() { - @Override - public void run() { - try { - mJsonWriter.beginObject(); - mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); - mJsonWriter.name(UPTIME_KEY).value(SystemClock.uptimeMillis()); - mJsonWriter.name(EVENT_TYPE_KEY).value(keys[0]); - final int length = values.length; - for (int i = 0; i < length; i++) { - mJsonWriter.name(keys[i + 1]); - Object value = values[i]; - if (value instanceof String) { - mJsonWriter.value((String) value); - } else if (value instanceof Number) { - mJsonWriter.value((Number) value); - } else if (value instanceof Boolean) { - mJsonWriter.value((Boolean) value); - } else if (value instanceof CompletionInfo[]) { - CompletionInfo[] ci = (CompletionInfo[]) value; - mJsonWriter.beginArray(); - for (int j = 0; j < ci.length; j++) { - mJsonWriter.value(ci[j].toString()); - } - mJsonWriter.endArray(); - } else if (value instanceof SharedPreferences) { - SharedPreferences prefs = (SharedPreferences) value; - mJsonWriter.beginObject(); - for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) { - mJsonWriter.name(entry.getKey()); - final Object innerValue = entry.getValue(); - if (innerValue == null) { - mJsonWriter.nullValue(); - } else if (innerValue instanceof Boolean) { - mJsonWriter.value((Boolean) innerValue); - } else if (innerValue instanceof Number) { - mJsonWriter.value((Number) innerValue); - } else { - mJsonWriter.value(innerValue.toString()); - } - } - mJsonWriter.endObject(); - } else if (value instanceof Key[]) { - Key[] keys = (Key[]) value; - mJsonWriter.beginArray(); - for (Key key : keys) { - mJsonWriter.beginObject(); - mJsonWriter.name("code").value(key.mCode); - mJsonWriter.name("altCode").value(key.mAltCode); - mJsonWriter.name("x").value(key.mX); - mJsonWriter.name("y").value(key.mY); - mJsonWriter.name("w").value(key.mWidth); - mJsonWriter.name("h").value(key.mHeight); - mJsonWriter.endObject(); - } - mJsonWriter.endArray(); - } else if (value instanceof SuggestedWords) { - SuggestedWords words = (SuggestedWords) value; - mJsonWriter.beginObject(); - mJsonWriter.name("typedWordValid").value(words.mTypedWordValid); - mJsonWriter.name("hasAutoCorrectionCandidate") - .value(words.mHasAutoCorrectionCandidate); - mJsonWriter.name("isPunctuationSuggestions") - .value(words.mIsPunctuationSuggestions); - mJsonWriter.name("allowsToBeAutoCorrected") - .value(words.mAllowsToBeAutoCorrected); - mJsonWriter.name("isObsoleteSuggestions") - .value(words.mIsObsoleteSuggestions); - mJsonWriter.name("isPrediction") - .value(words.mIsPrediction); - mJsonWriter.name("words"); - mJsonWriter.beginArray(); - final int size = words.size(); - for (int j = 0; j < size; j++) { - SuggestedWordInfo wordInfo = words.getWordInfo(j); - mJsonWriter.value(wordInfo.toString()); - } - mJsonWriter.endArray(); - mJsonWriter.endObject(); - } else if (value == null) { - mJsonWriter.nullValue(); - } else { - Log.w(TAG, "Unrecognized type to be logged: " + - (value == null ? "<null>" : value.getClass().getName())); - mJsonWriter.nullValue(); - } + mCurrentLogUnit.addLogAtom(keys, values, true); + } + + /** + * Buffer a research log event, flaggint it as not privacy-sensitive. + * + * This event contains no potentially private information. Even if the word that this event + * is privacy-sensitive, this event can still safely be sent to the output log. The system + * waits until the containing word is known so that this event can be written in the proper + * temporal order with other events that may be privacy sensitive. + * + * @param keys an array containing a descriptive name for the event, followed by the keys + * @param values an array of values, either a String or Number. length should be one + * less than the keys array + */ + private synchronized void enqueueEvent(final String[] keys, final Object[] values) { + assert values.length + 1 == keys.length; + mCurrentLogUnit.addLogAtom(keys, values, false); + } + + /* package for test */ boolean isPrivacyThreat(String word) { + // currently: word not in dictionary or contains numbers. + if (TextUtils.isEmpty(word)) { + return false; + } + final int length = word.length(); + boolean hasLetter = false; + for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { + final int codePoint = Character.codePointAt(word, i); + if (Character.isDigit(codePoint)) { + return true; + } + if (Character.isLetter(codePoint)) { + hasLetter = true; + break; // Word may contain digits, but will only be allowed if in the dictionary. + } + } + if (hasLetter) { + if (mDictionary == null && mSuggest != null && mSuggest.hasMainDictionary()) { + mDictionary = mSuggest.getMainDictionary(); + } + if (mDictionary == null) { + // Can't access dictionary. Assume privacy threat. + return true; + } + return !(mDictionary.isValidWord(word)); + } + // No letters, no numbers. Punctuation, space, or something else. + return false; + } + + /** + * Write out enqueued LogEvents to the log, possibly dropping privacy sensitive events. + */ + /* package for test */ synchronized void flushQueue(boolean removePotentiallyPrivateEvents) { + if (isAllowedToLog()) { + mCurrentLogUnit.setRemovePotentiallyPrivateEvents(removePotentiallyPrivateEvents); + mLoggingHandler.post(mCurrentLogUnit); + mCurrentLogUnit = new LogUnit(); + } + } + + private synchronized void outputEvent(final String[] keys, final Object[] values) { + try { + mJsonWriter.beginObject(); + mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); + mJsonWriter.name(UPTIME_KEY).value(SystemClock.uptimeMillis()); + mJsonWriter.name(EVENT_TYPE_KEY).value(keys[0]); + final int length = values.length; + for (int i = 0; i < length; i++) { + mJsonWriter.name(keys[i + 1]); + Object value = values[i]; + if (value instanceof String) { + mJsonWriter.value((String) value); + } else if (value instanceof Number) { + mJsonWriter.value((Number) value); + } else if (value instanceof Boolean) { + mJsonWriter.value((Boolean) value); + } else if (value instanceof CompletionInfo[]) { + CompletionInfo[] ci = (CompletionInfo[]) value; + mJsonWriter.beginArray(); + for (int j = 0; j < ci.length; j++) { + mJsonWriter.value(ci[j].toString()); + } + mJsonWriter.endArray(); + } else if (value instanceof SharedPreferences) { + SharedPreferences prefs = (SharedPreferences) value; + mJsonWriter.beginObject(); + for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) { + mJsonWriter.name(entry.getKey()); + final Object innerValue = entry.getValue(); + if (innerValue == null) { + mJsonWriter.nullValue(); + } else if (innerValue instanceof Boolean) { + mJsonWriter.value((Boolean) innerValue); + } else if (innerValue instanceof Number) { + mJsonWriter.value((Number) innerValue); + } else { + mJsonWriter.value(innerValue.toString()); } + } + mJsonWriter.endObject(); + } else if (value instanceof Key[]) { + Key[] keyboardKeys = (Key[]) value; + mJsonWriter.beginArray(); + for (Key keyboardKey : keyboardKeys) { + mJsonWriter.beginObject(); + mJsonWriter.name("code").value(keyboardKey.mCode); + mJsonWriter.name("altCode").value(keyboardKey.mAltCode); + mJsonWriter.name("x").value(keyboardKey.mX); + mJsonWriter.name("y").value(keyboardKey.mY); + mJsonWriter.name("w").value(keyboardKey.mWidth); + mJsonWriter.name("h").value(keyboardKey.mHeight); mJsonWriter.endObject(); - } catch (IOException e) { - e.printStackTrace(); - Log.w(TAG, "Error in JsonWriter; disabling logging"); - try { - mJsonWriter.close(); - } catch (IllegalStateException e1) { - // assume that this is just the json not being terminated properly. - // ignore - } catch (IOException e1) { - e1.printStackTrace(); - } finally { - mJsonWriter = NULL_JSON_WRITER; - } } + mJsonWriter.endArray(); + } else if (value instanceof SuggestedWords) { + SuggestedWords words = (SuggestedWords) value; + mJsonWriter.beginObject(); + mJsonWriter.name("typedWordValid").value(words.mTypedWordValid); + mJsonWriter.name("hasAutoCorrectionCandidate") + .value(words.mHasAutoCorrectionCandidate); + mJsonWriter.name("isPunctuationSuggestions") + .value(words.mIsPunctuationSuggestions); + mJsonWriter.name("allowsToBeAutoCorrected") + .value(words.mAllowsToBeAutoCorrected); + mJsonWriter.name("isObsoleteSuggestions") + .value(words.mIsObsoleteSuggestions); + mJsonWriter.name("isPrediction") + .value(words.mIsPrediction); + mJsonWriter.name("words"); + mJsonWriter.beginArray(); + final int size = words.size(); + for (int j = 0; j < size; j++) { + SuggestedWordInfo wordInfo = words.getWordInfo(j); + mJsonWriter.value(wordInfo.toString()); + } + mJsonWriter.endArray(); + mJsonWriter.endObject(); + } else if (value == null) { + mJsonWriter.nullValue(); + } else { + Log.w(TAG, "Unrecognized type to be logged: " + + (value == null ? "<null>" : value.getClass().getName())); + mJsonWriter.nullValue(); } - }); + } + mJsonWriter.endObject(); + } catch (IOException e) { + e.printStackTrace(); + Log.w(TAG, "Error in JsonWriter; disabling logging"); + try { + mJsonWriter.close(); + } catch (IllegalStateException e1) { + // assume that this is just the json not being terminated properly. + // ignore + } catch (IOException e1) { + e1.printStackTrace(); + } finally { + mJsonWriter = NULL_JSON_WRITER; + } } } + private static class LogUnit implements Runnable { + private final List<String[]> mKeysList = new ArrayList<String[]>(); + private final List<Object[]> mValuesList = new ArrayList<Object[]>(); + private final List<Boolean> mIsPotentiallyPrivate = new ArrayList<Boolean>(); + private boolean mRemovePotentiallyPrivateEvents = true; + + private void addLogAtom(final String[] keys, final Object[] values, + final Boolean isPotentiallyPrivate) { + mKeysList.add(keys); + mValuesList.add(values); + mIsPotentiallyPrivate.add(isPotentiallyPrivate); + } + + void setRemovePotentiallyPrivateEvents(boolean removePotentiallyPrivateEvents) { + mRemovePotentiallyPrivateEvents = removePotentiallyPrivateEvents; + } + + @Override + public void run() { + final int numAtoms = mKeysList.size(); + for (int atomIndex = 0; atomIndex < numAtoms; atomIndex++) { + if (mRemovePotentiallyPrivateEvents && mIsPotentiallyPrivate.get(atomIndex)) { + continue; + } + final String[] keys = mKeysList.get(atomIndex); + final Object[] values = mValuesList.get(atomIndex); + ResearchLogger.getInstance().outputEvent(keys, values); + } + } + } + + private static int scrubDigitFromCodePoint(int codePoint) { + return Character.isDigit(codePoint) ? DIGIT_REPLACEMENT_CODEPOINT : codePoint; + } + + /* package for test */ static String scrubDigitsFromString(String s) { + StringBuilder sb = null; + final int length = s.length(); + for (int i = 0; i < length; i = s.offsetByCodePoints(i, 1)) { + final int codePoint = Character.codePointAt(s, i); + if (Character.isDigit(codePoint)) { + if (sb == null) { + sb = new StringBuilder(length); + sb.append(s.substring(0, i)); + } + sb.appendCodePoint(DIGIT_REPLACEMENT_CODEPOINT); + } else { + if (sb != null) { + sb.appendCodePoint(codePoint); + } + } + } + if (sb == null) { + return s; + } else { + return sb.toString(); + } + } + + private String scrubWord(String word) { + if (mDictionary == null) { + return WORD_REPLACEMENT_STRING; + } + if (mDictionary.isValidWord(word)) { + return word; + } + return WORD_REPLACEMENT_STRING; + } + private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT = { "LatinKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size", "pressure" @@ -460,7 +615,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { actionString, eventTime, id, x, y, size, pressure }; - getInstance().writeEvent(EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT, values); + getInstance().enqueuePotentiallyPrivateEvent( + EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT, values); } } @@ -469,9 +625,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang }; public static void latinIME_onCodeInput(final int code, final int x, final int y) { final Object[] values = { - Keyboard.printableCode(code), x, y + Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y }; - getInstance().writeEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values); + getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values); } private static final String[] EVENTKEYS_CORRECTION = { @@ -480,9 +636,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void logCorrection(final String subgroup, final String before, final String after, final int position) { final Object[] values = { - subgroup, before, after, position + subgroup, scrubDigitsFromString(before), scrubDigitsFromString(after), position }; - getInstance().writeEvent(EVENTKEYS_CORRECTION, values); + getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_CORRECTION, values); } private static final String[] EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION = { @@ -491,19 +647,25 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void latinIME_commitCurrentAutoCorrection(final String typedWord, final String autoCorrection) { final Object[] values = { - typedWord, autoCorrection + scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection) }; - getInstance().writeEvent(EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values); + final ResearchLogger researchLogger = getInstance(); + researchLogger.enqueuePotentiallyPrivateEvent( + EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values); + researchLogger.flushQueue(researchLogger.isPrivacyThreat(autoCorrection)); } private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = { "LatinIMECommitText", "typedWord" }; public static void latinIME_commitText(final CharSequence typedWord) { + final String scrubbedWord = scrubDigitsFromString(typedWord.toString()); final Object[] values = { - typedWord.toString() + scrubbedWord }; - getInstance().writeEvent(EVENTKEYS_LATINIME_COMMITTEXT, values); + final ResearchLogger researchLogger = getInstance(); + researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values); + researchLogger.flushQueue(researchLogger.isPrivacyThreat(scrubbedWord)); } private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = { @@ -513,14 +675,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { length }; - getInstance().writeEvent(EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT, values); + getInstance().enqueueEvent(EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT, values); } private static final String[] EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD = { "LatinIMEDoubleSpaceAutoPeriod" }; public static void latinIME_doubleSpaceAutoPeriod() { - getInstance().writeEvent(EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD, EVENTKEYS_NULLVALUES); + getInstance().enqueueEvent(EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD, EVENTKEYS_NULLVALUES); } private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = { @@ -531,7 +693,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { applicationSpecifiedCompletions }; - getInstance().writeEvent(EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS, values); + getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS, + values); } /* package */ static boolean getAndClearLatinIMEExpectingUpdateSelection() { @@ -552,27 +715,35 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang ic.setSelection(savedSelectionStart, savedSelectionEnd); ic.endBatchEdit(); sLatinIMEExpectingUpdateSelection = true; - Object[] values = new Object[2]; - if (TextUtils.isEmpty(charSequence)) { - values[0] = false; - values[1] = ""; - } else { - if (charSequence.length() > MAX_INPUTVIEW_LENGTH_TO_CAPTURE) { - int length = MAX_INPUTVIEW_LENGTH_TO_CAPTURE; - // do not cut in the middle of a supplementary character - final char c = charSequence.charAt(length - 1); - if (Character.isHighSurrogate(c)) { - length--; - } - final CharSequence truncatedCharSequence = charSequence.subSequence(0, length); - values[0] = true; - values[1] = truncatedCharSequence.toString(); - } else { + final Object[] values = new Object[2]; + if (OUTPUT_ENTIRE_BUFFER) { + if (TextUtils.isEmpty(charSequence)) { values[0] = false; - values[1] = charSequence.toString(); + values[1] = ""; + } else { + if (charSequence.length() > MAX_INPUTVIEW_LENGTH_TO_CAPTURE) { + int length = MAX_INPUTVIEW_LENGTH_TO_CAPTURE; + // do not cut in the middle of a supplementary character + final char c = charSequence.charAt(length - 1); + if (Character.isHighSurrogate(c)) { + length--; + } + final CharSequence truncatedCharSequence = charSequence.subSequence(0, + length); + values[0] = true; + values[1] = truncatedCharSequence.toString(); + } else { + values[0] = false; + values[1] = charSequence.toString(); + } } + } else { + values[0] = true; + values[1] = ""; } - getInstance().writeEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values); + final ResearchLogger researchLogger = getInstance(); + researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values); + researchLogger.flushQueue(true); // Play it safe. Remove privacy-sensitive events. } } @@ -588,7 +759,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId, Build.DISPLAY, Build.MODEL, prefs, OUTPUT_FORMAT_VERSION }; - getInstance().writeEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values); + getInstance().enqueueEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values); } } @@ -622,12 +793,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang word = range.mWord; } } + final ResearchLogger researchLogger = getInstance(); + final String scrubbedWord = researchLogger.scrubWord(word); final Object[] values = { lastSelectionStart, lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart, composingSpanEnd, expectingUpdateSelection, - expectingUpdateSelectionFromLogger, word + expectingUpdateSelectionFromLogger, scrubbedWord }; - getInstance().writeEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values); + researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values); } private static final String[] EVENTKEYS_LATINIME_PERFORMEDITORACTION = { @@ -637,7 +810,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { imeActionNext }; - getInstance().writeEvent(EVENTKEYS_LATINIME_PERFORMEDITORACTION, values); + getInstance().enqueueEvent(EVENTKEYS_LATINIME_PERFORMEDITORACTION, values); } private static final String[] EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION = { @@ -648,7 +821,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { index, cs, x, y }; - getInstance().writeEvent(EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values); + final ResearchLogger researchLogger = getInstance(); + researchLogger.enqueuePotentiallyPrivateEvent( + EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values); + researchLogger.flushQueue(researchLogger.isPrivacyThreat(cs.toString())); } private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = { @@ -657,9 +833,13 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void latinIME_pickSuggestionManually(final String replacedWord, final int index, CharSequence suggestion, int x, int y) { final Object[] values = { - replacedWord, index, suggestion, x, y + scrubDigitsFromString(replacedWord), index, suggestion == null ? null : + scrubDigitsFromString(suggestion.toString()), x, y }; - getInstance().writeEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY, values); + final ResearchLogger researchLogger = getInstance(); + researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY, + values); + researchLogger.flushQueue(researchLogger.isPrivacyThreat(suggestion.toString())); } private static final String[] EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION = { @@ -670,14 +850,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { index, suggestion, x, y }; - getInstance().writeEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values); + getInstance().enqueueEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values); } private static final String[] EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT = { "LatinIMERevertDoubleSpaceWhileInBatchEdit" }; public static void latinIME_revertDoubleSpaceWhileInBatchEdit() { - getInstance().writeEvent(EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT, + getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT, EVENTKEYS_NULLVALUES); } @@ -685,7 +865,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang "LatinIMERevertSwapPunctuation" }; public static void latinIME_revertSwapPunctuation() { - getInstance().writeEvent(EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION, EVENTKEYS_NULLVALUES); + getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION, EVENTKEYS_NULLVALUES); } private static final String[] EVENTKEYS_LATINIME_SENDKEYCODEPOINT = { @@ -693,16 +873,16 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang }; public static void latinIME_sendKeyCodePoint(final int code) { final Object[] values = { - code + Keyboard.printableCode(scrubDigitFromCodePoint(code)) }; - getInstance().writeEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values); + getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values); } private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT = { "LatinIMESwapSwapperAndSpaceWhileInBatchEdit" }; public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() { - getInstance().writeEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT, + getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT, EVENTKEYS_NULLVALUES); } @@ -710,14 +890,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang "LatinIMESwitchToKeyboardView" }; public static void latinIME_switchToKeyboardView() { - getInstance().writeEvent(EVENTKEYS_LATINIME_SWITCHTOKEYBOARDVIEW, EVENTKEYS_NULLVALUES); + getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWITCHTOKEYBOARDVIEW, EVENTKEYS_NULLVALUES); } private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_ONLONGPRESS = { "LatinKeyboardViewOnLongPress" }; public static void latinKeyboardView_onLongPress() { - getInstance().writeEvent(EVENTKEYS_LATINKEYBOARDVIEW_ONLONGPRESS, EVENTKEYS_NULLVALUES); + getInstance().enqueueEvent(EVENTKEYS_LATINKEYBOARDVIEW_ONLONGPRESS, EVENTKEYS_NULLVALUES); } private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_SETKEYBOARD = { @@ -729,6 +909,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void latinKeyboardView_setKeyboard(final Keyboard keyboard) { if (keyboard != null) { final KeyboardId kid = keyboard.mId; + final boolean isPasswordView = kid.passwordInput(); final Object[] values = { KeyboardId.elementIdToName(kid.mElementId), kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), @@ -739,7 +920,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey, - kid.passwordInput(), + isPasswordView, kid.mShortcutKeyEnabled, kid.mHasShortcutKey, kid.mLanguageSwitchKeyEnabled, @@ -748,7 +929,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang keyboard.mOccupiedHeight, keyboard.mKeys }; - getInstance().writeEvent(EVENTKEYS_LATINKEYBOARDVIEW_SETKEYBOARD, values); + getInstance().enqueueEvent(EVENTKEYS_LATINKEYBOARDVIEW_SETKEYBOARD, values); + getInstance().setIsPasswordView(isPasswordView); } } @@ -759,14 +941,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { originallyTypedWord }; - getInstance().writeEvent(EVENTKEYS_LATINIME_REVERTCOMMIT, values); + getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_REVERTCOMMIT, values); } private static final String[] EVENTKEYS_POINTERTRACKER_CALLLISTENERONCANCELINPUT = { "PointerTrackerCallListenerOnCancelInput" }; public static void pointerTracker_callListenerOnCancelInput() { - getInstance().writeEvent(EVENTKEYS_POINTERTRACKER_CALLLISTENERONCANCELINPUT, + getInstance().enqueueEvent(EVENTKEYS_POINTERTRACKER_CALLLISTENERONCANCELINPUT, EVENTKEYS_NULLVALUES); } @@ -780,10 +962,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (key != null) { CharSequence outputText = key.mOutputText; final Object[] values = { - Keyboard.printableCode(code), outputText, x, y, ignoreModifierKey, altersCode, - key.isEnabled() + Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null + : scrubDigitsFromString(outputText.toString()), + x, y, ignoreModifierKey, altersCode, key.isEnabled() }; - getInstance().writeEvent(EVENTKEYS_POINTERTRACKER_CALLLISTENERONCODEINPUT, values); + getInstance().enqueuePotentiallyPrivateEvent( + EVENTKEYS_POINTERTRACKER_CALLLISTENERONCODEINPUT, values); } } @@ -795,10 +979,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final boolean withSliding, final boolean ignoreModifierKey) { if (key != null) { final Object[] values = { - Keyboard.printableCode(primaryCode), withSliding, ignoreModifierKey, - key.isEnabled() + Keyboard.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding, + ignoreModifierKey, key.isEnabled() }; - getInstance().writeEvent(EVENTKEYS_POINTERTRACKER_CALLLISTENERONRELEASE, values); + getInstance().enqueuePotentiallyPrivateEvent( + EVENTKEYS_POINTERTRACKER_CALLLISTENERONRELEASE, values); } } @@ -809,7 +994,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { deltaT, distanceSquared }; - getInstance().writeEvent(EVENTKEYS_POINTERTRACKER_ONDOWNEVENT, values); + getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONDOWNEVENT, values); } private static final String[] EVENTKEYS_POINTERTRACKER_ONMOVEEVENT = { @@ -820,7 +1005,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { x, y, lastX, lastY }; - getInstance().writeEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values); + getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values); } private static final String[] EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = { @@ -831,8 +1016,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { me.toString() }; - getInstance().writeEvent(EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT, - values); + getInstance().enqueuePotentiallyPrivateEvent( + EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT, values); } } @@ -844,7 +1029,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values = { suggestedWords }; - getInstance().writeEvent(EVENTKEYS_SUGGESTIONSVIEW_SETSUGGESTIONS, values); + getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_SUGGESTIONSVIEW_SETSUGGESTIONS, + values); } } @@ -852,6 +1038,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang "UserTimestamp" }; public void userTimestamp() { - getInstance().writeEvent(EVENTKEYS_USER_TIMESTAMP, EVENTKEYS_NULLVALUES); + getInstance().enqueueEvent(EVENTKEYS_USER_TIMESTAMP, EVENTKEYS_NULLVALUES); } } diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index c9ff0a5a8..4c67b4957 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -69,9 +69,7 @@ public class Settings extends InputMethodSettingsFragment public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY = "pref_key_preview_popup_dismiss_delay"; public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict"; - public static final String PREF_BIGRAM_SUGGESTION = "next_word_suggestion"; public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction"; - public static final String PREF_KEY_ENABLE_SPAN_INSERT = "enable_span_insert"; public static final String PREF_VIBRATION_DURATION_SETTINGS = "pref_vibration_duration_settings"; public static final String PREF_KEYPRESS_SOUND_VOLUME = @@ -87,9 +85,7 @@ public class Settings extends InputMethodSettingsFragment private ListPreference mShowCorrectionSuggestionsPreference; private ListPreference mAutoCorrectionThresholdPreference; private ListPreference mKeyPreviewPopupDismissDelay; - // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary - private CheckBoxPreference mBigramSuggestion; - // Prediction: use bigrams to predict the next word when there is no input for it yet + // Use bigrams to predict the next word when there is no input for it yet private CheckBoxPreference mBigramPrediction; private Preference mDebugSettingsPreference; @@ -100,7 +96,6 @@ public class Settings extends InputMethodSettingsFragment final String autoCorrectionOff = getResources().getString( R.string.auto_correction_threshold_mode_index_off); final String currentSetting = mAutoCorrectionThresholdPreference.getValue(); - mBigramSuggestion.setEnabled(!currentSetting.equals(autoCorrectionOff)); if (null != mBigramPrediction) { mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff)); } @@ -124,7 +119,6 @@ public class Settings extends InputMethodSettingsFragment mAutoCorrectionThresholdPreference = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD); - mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTION); mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS); mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS); if (mDebugSettingsPreference != null) { @@ -150,9 +144,9 @@ public class Settings extends InputMethodSettingsFragment } if (!VibratorUtils.getInstance(context).hasVibrator()) { - generalSettings.removePreference(findPreference(PREF_VIBRATE_ON)); final PreferenceGroup advancedSettings = (PreferenceGroup) findPreference(PREF_ADVANCED_SETTINGS); + generalSettings.removePreference(findPreference(PREF_VIBRATE_ON)); if (null != advancedSettings) { // Theoretically advancedSettings cannot be null advancedSettings.removePreference(findPreference(PREF_VIBRATION_DURATION_SETTINGS)); } @@ -164,15 +158,6 @@ public class Settings extends InputMethodSettingsFragment generalSettings.removePreference(findPreference(PREF_POPUP_ON)); } - final boolean showBigramSuggestionsOption = res.getBoolean( - R.bool.config_enable_next_word_suggestions_option); - if (!showBigramSuggestionsOption) { - textCorrectionGroup.removePreference(mBigramSuggestion); - if (null != mBigramPrediction) { - textCorrectionGroup.removePreference(mBigramPrediction); - } - } - final CheckBoxPreference includeOtherImesInLanguageSwitchList = (CheckBoxPreference)findPreference(PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST); includeOtherImesInLanguageSwitchList.setEnabled( diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 6a79aa611..ef423f19b 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -76,24 +76,24 @@ public class SettingsValues { @SuppressWarnings("unused") // TODO: Use this private final String mKeyPreviewPopupDismissDelayRawValue; public final boolean mUseContactsDict; - // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary - public final boolean mBigramSuggestionEnabled; - // Prediction: use bigrams to predict the next word when there is no input for it yet + // Use bigrams to predict the next word when there is no input for it yet public final boolean mBigramPredictionEnabled; - public final boolean mEnableSuggestionSpanInsertion; @SuppressWarnings("unused") // TODO: Use this private final int mVibrationDurationSettingsRawValue; @SuppressWarnings("unused") // TODO: Use this private final float mKeypressSoundVolumeRawValue; private final InputMethodSubtype[] mAdditionalSubtypes; + // From the input box + private final InputAttributes mInputAttributes; + // Deduced settings public final int mKeypressVibrationDuration; public final float mFxVolume; public final int mKeyPreviewPopupDismissDelay; private final boolean mAutoCorrectEnabled; public final float mAutoCorrectionThreshold; - public final int mCorrectionMode; + public final boolean mCorrectionEnabled; public final int mSuggestionVisibility; private final boolean mVoiceKeyEnabled; private final boolean mVoiceKeyOnMain; @@ -125,6 +125,13 @@ public class SettingsValues { mSymbolsExcludedFromWordSeparators, res); mHintToSaveText = context.getText(R.string.hint_add_to_dictionary); + // Store the input attributes + if (null == inputAttributes) { + mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */); + } else { + mInputAttributes = inputAttributes; + } + // Get the settings preferences mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); mVibrateOn = isVibrateOn(context, prefs, res); @@ -147,12 +154,7 @@ public class SettingsValues { Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout))); mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); mAutoCorrectEnabled = isAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue); - mBigramSuggestionEnabled = mAutoCorrectEnabled - && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled); - mBigramPredictionEnabled = mBigramSuggestionEnabled - && isBigramPredictionEnabled(prefs, res); - mEnableSuggestionSpanInsertion = - prefs.getBoolean(Settings.PREF_KEY_ENABLE_SPAN_INSERT, true); + mBigramPredictionEnabled = isBigramPredictionEnabled(prefs, res); mVibrationDurationSettingsRawValue = prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1); mKeypressSoundVolumeRawValue = prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f); @@ -167,18 +169,17 @@ public class SettingsValues { mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain); mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray( getPrefAdditionalSubtypes(prefs, res)); - mCorrectionMode = createCorrectionMode(inputAttributes); + mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect; mSuggestionVisibility = createSuggestionVisibility(res); } // Helper functions to create member values. private static SuggestedWords createSuggestPuncList(final String[] puncs) { - final ArrayList<SuggestedWords.SuggestedWordInfo> puncList = - new ArrayList<SuggestedWords.SuggestedWordInfo>(); + final ArrayList<SuggestedWordInfo> puncList = new ArrayList<SuggestedWordInfo>(); if (puncs != null) { for (final String puncSpec : puncs) { - puncList.add(new SuggestedWords.SuggestedWordInfo( - KeySpecParser.getLabel(puncSpec), SuggestedWordInfo.MAX_SCORE)); + puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec), + SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED)); } } return new SuggestedWords(puncList, @@ -202,13 +203,6 @@ public class SettingsValues { return wordSeparators; } - private int createCorrectionMode(final InputAttributes inputAttributes) { - final boolean shouldAutoCorrect = mAutoCorrectEnabled - && (null == inputAttributes || !inputAttributes.mInputTypeNoAutoCorrect); - if (mBigramSuggestionEnabled && shouldAutoCorrect) return Suggest.CORRECTION_FULL_BIGRAM; - return shouldAutoCorrect ? Suggest.CORRECTION_FULL : Suggest.CORRECTION_NONE; - } - private int createSuggestionVisibility(final Resources res) { final String suggestionVisiblityStr = mShowSuggestionsSetting; for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) { @@ -226,9 +220,18 @@ public class SettingsValues { res.getBoolean(R.bool.config_default_vibration_enabled)); } - public boolean isCorrectionOn() { - return mCorrectionMode == Suggest.CORRECTION_FULL - || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM; + public boolean isApplicationSpecifiedCompletionsOn() { + return mInputAttributes.mApplicationSpecifiedCompletionOn; + } + + public boolean isEditorActionNext() { + return mInputAttributes.mEditorAction == EditorInfo.IME_ACTION_NEXT; + } + + public boolean isSuggestionsRequested(final int displayOrientation) { + return mInputAttributes.mIsSettingsSuggestionStripOn + && (mCorrectionEnabled + || isSuggestionStripVisibleInOrientation(displayOrientation)); } public boolean isSuggestionStripVisibleInOrientation(final int orientation) { @@ -286,17 +289,6 @@ public class SettingsValues { R.integer.config_key_preview_linger_timeout)))); } - private static boolean isBigramSuggestionEnabled(final SharedPreferences sp, - final Resources resources, final boolean autoCorrectEnabled) { - final boolean showBigramSuggestionsOption = resources.getBoolean( - R.bool.config_enable_next_word_suggestions_option); - if (!showBigramSuggestionsOption) { - return autoCorrectEnabled; - } - return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTION, resources.getBoolean( - R.bool.config_default_next_word_suggestions)); - } - private static boolean isBigramPredictionEnabled(final SharedPreferences sp, final Resources resources) { return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean( @@ -418,4 +410,9 @@ public class SettingsValues { final String newStr = Utils.localeAndTimeHashMapToStr(map); prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply(); } + + // For debug. + public String getInputAttributesDebugString() { + return mInputAttributes.toString(); + } } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 68b7b913f..892245402 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -39,9 +39,10 @@ public class Suggest implements Dictionary.WordCallback { public static final int APPROX_MAX_WORD_LENGTH = 32; + // TODO: rename this to CORRECTION_OFF public static final int CORRECTION_NONE = 0; + // TODO: rename this to CORRECTION_ON public static final int CORRECTION_FULL = 1; - public static final int CORRECTION_FULL_BIGRAM = 2; // It seems the following values are only used for logging. public static final int DIC_USER_TYPED = 0; @@ -65,7 +66,7 @@ public class Suggest implements Dictionary.WordCallback { private static final boolean DBG = LatinImeLogger.sDBG; - private boolean mHasMainDictionary; + private Dictionary mMainDictionary; private ContactsBinaryDictionary mContactsDict; private WhitelistDictionary mWhiteListDictionary; private final ConcurrentHashMap<String, Dictionary> mUnigramDictionaries = @@ -73,7 +74,7 @@ public class Suggest implements Dictionary.WordCallback { private final ConcurrentHashMap<String, Dictionary> mBigramDictionaries = new ConcurrentHashMap<String, Dictionary>(); - private int mPrefMaxSuggestions = 18; + public static final int MAX_SUGGESTIONS = 18; private static final int PREF_MAX_BIGRAMS = 60; @@ -98,7 +99,7 @@ public class Suggest implements Dictionary.WordCallback { final long startOffset, final long length, final Locale locale) { final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary, startOffset, length /* useFullEditDistance */, false, locale); - mHasMainDictionary = null != mainDict; + mMainDictionary = mainDict; addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict); addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict); initWhitelistAndAutocorrectAndPool(context, locale); @@ -129,15 +130,15 @@ public class Suggest implements Dictionary.WordCallback { } public void resetMainDict(final Context context, final Locale locale) { - mHasMainDictionary = false; + mMainDictionary = null; new Thread("InitializeBinaryDictionary") { @Override public void run() { final DictionaryCollection newMainDict = DictionaryFactory.createMainDictionaryFromManager(context, locale); - mHasMainDictionary = null != newMainDict && !newMainDict.isEmpty(); addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict); addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict); + mMainDictionary = newMainDict; } }.start(); } @@ -145,7 +146,11 @@ public class Suggest implements Dictionary.WordCallback { // The main dictionary could have been loaded asynchronously. Don't cache the return value // of this method. public boolean hasMainDictionary() { - return mHasMainDictionary; + return null != mMainDictionary && mMainDictionary.isInitialized(); + } + + public Dictionary getMainDictionary() { + return mMainDictionary; } public ContactsBinaryDictionary getContactsDictionary() { @@ -217,7 +222,7 @@ public class Suggest implements Dictionary.WordCallback { mIsFirstCharCapitalized = false; mIsAllUpperCase = false; mTrailingSingleQuotesCount = 0; - mSuggestions = new ArrayList<SuggestedWordInfo>(mPrefMaxSuggestions); + mSuggestions = new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS); // Treating USER_TYPED as UNIGRAM suggestion for logging now. LatinImeLogger.onAddSuggestedWord("", Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM); @@ -228,7 +233,7 @@ public class Suggest implements Dictionary.WordCallback { getAllBigrams(prevWordForBigram, sEmptyWordComposer); // Nothing entered: return all bigrams for the previous word - int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions); + int insertCount = Math.min(mBigramSuggestions.size(), MAX_SUGGESTIONS); for (int i = 0; i < insertCount; ++i) { addBigramToSuggestions(mBigramSuggestions.get(i)); } @@ -247,12 +252,12 @@ public class Suggest implements Dictionary.WordCallback { // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder public SuggestedWords getSuggestedWords( final WordComposer wordComposer, CharSequence prevWordForBigram, - final ProximityInfo proximityInfo, final int correctionMode) { + final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) { LatinImeLogger.onStartSuggestion(prevWordForBigram); mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsAllUpperCase = wordComposer.isAllUpperCase(); mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); - mSuggestions = new ArrayList<SuggestedWordInfo>(mPrefMaxSuggestions); + mSuggestions = new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS); final String typedWord = wordComposer.getTypedWord(); final String consideredWord = mTrailingSingleQuotesCount > 0 @@ -262,7 +267,7 @@ public class Suggest implements Dictionary.WordCallback { LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM); mConsideredWord = consideredWord; - if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) { + if (wordComposer.size() <= 1 && isCorrectionEnabled) { // At first character typed, search only the bigrams mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS); @@ -270,7 +275,7 @@ public class Suggest implements Dictionary.WordCallback { getAllBigrams(prevWordForBigram, wordComposer); if (TextUtils.isEmpty(consideredWord)) { // Nothing entered: return all bigrams for the previous word - int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions); + int insertCount = Math.min(mBigramSuggestions.size(), MAX_SUGGESTIONS); for (int i = 0; i < insertCount; ++i) { addBigramToSuggestions(mBigramSuggestions.get(i)); } @@ -289,7 +294,7 @@ public class Suggest implements Dictionary.WordCallback { if (bigramSuggestionFirstChar == currentChar || bigramSuggestionFirstChar == currentCharUpper) { addBigramToSuggestions(bigramSuggestion); - if (++count > mPrefMaxSuggestions) break; + if (++count > MAX_SUGGESTIONS) break; } } } @@ -319,7 +324,7 @@ public class Suggest implements Dictionary.WordCallback { mIsFirstCharCapitalized, mWhiteListDictionary.getWhitelistedWord(consideredWord)); final boolean hasAutoCorrection; - if (CORRECTION_FULL == correctionMode || CORRECTION_FULL_BIGRAM == correctionMode) { + if (isCorrectionEnabled) { final CharSequence autoCorrection = AutoCorrection.computeAutoCorrectionWord(mUnigramDictionaries, wordComposer, mSuggestions, consideredWord, mAutoCorrectionThreshold, @@ -335,15 +340,16 @@ public class Suggest implements Dictionary.WordCallback { for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) { sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE); } - mSuggestions.add(0, new SuggestedWordInfo( - sb.toString(), SuggestedWordInfo.MAX_SCORE)); + mSuggestions.add(0, new SuggestedWordInfo(sb.toString(), + SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST)); } else { - mSuggestions.add(0, new SuggestedWordInfo( - whitelistedWord, SuggestedWordInfo.MAX_SCORE)); + mSuggestions.add(0, new SuggestedWordInfo(whitelistedWord, + SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST)); } } - mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE)); + mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, + SuggestedWordInfo.KIND_TYPED)); SuggestedWordInfo.removeDups(mSuggestions); final ArrayList<SuggestedWordInfo> suggestionsList; @@ -365,10 +371,10 @@ public class Suggest implements Dictionary.WordCallback { // language, and it will unexpectedly auto-correct. For example, if the user types in // English with no dictionary and has a "Will" in their contact list, "will" would // always auto-correct to "Will" which is unwanted. Hence, no main dict => no auto-correct. - && mHasMainDictionary; + && hasMainDictionary(); boolean autoCorrectionAvailable = hasAutoCorrection; - if (correctionMode == CORRECTION_FULL || correctionMode == CORRECTION_FULL_BIGRAM) { + if (isCorrectionEnabled) { autoCorrectionAvailable |= !allowsToBeAutoCorrected; } // Don't auto-correct words with multiple capital letter @@ -443,7 +449,7 @@ public class Suggest implements Dictionary.WordCallback { prefMaxSuggestions = PREF_MAX_BIGRAMS; } else { suggestions = mSuggestions; - prefMaxSuggestions = mPrefMaxSuggestions; + prefMaxSuggestions = MAX_SUGGESTIONS; } int pos = 0; @@ -495,7 +501,8 @@ public class Suggest implements Dictionary.WordCallback { for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) { sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE); } - suggestions.add(pos, new SuggestedWordInfo(sb, score)); + // TODO: figure out what type of suggestion this is + suggestions.add(pos, new SuggestedWordInfo(sb, score, SuggestedWordInfo.KIND_CORRECTION)); if (suggestions.size() > prefMaxSuggestions) { suggestions.remove(prefMaxSuggestions); } else { @@ -511,7 +518,7 @@ public class Suggest implements Dictionary.WordCallback { for (final Dictionary dictionary : dictionaries) { dictionary.close(); } - mHasMainDictionary = false; + mMainDictionary = null; } // TODO: Resolve the inconsistencies between the native auto correction algorithms and diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 497fd3bfa..1ed91fe71 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -91,7 +91,8 @@ public class SuggestedWords { final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>(); for (CompletionInfo info : infos) { if (null != info && info.getText() != null) { - result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE)); + result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE, + SuggestedWordInfo.KIND_APP_DEFINED)); } } return result; @@ -103,7 +104,8 @@ public class SuggestedWords { final CharSequence typedWord, final SuggestedWords previousSuggestions) { final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>(); final HashSet<String> alreadySeen = new HashSet<String>(); - suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE)); + suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, + SuggestedWordInfo.KIND_TYPED)); alreadySeen.add(typedWord.toString()); final int previousSize = previousSuggestions.size(); for (int pos = 1; pos < previousSize; pos++) { @@ -120,16 +122,25 @@ public class SuggestedWords { public static class SuggestedWordInfo { public static final int MAX_SCORE = Integer.MAX_VALUE; + public static final int KIND_TYPED = 0; // What user typed + public static final int KIND_CORRECTION = 1; // Simple correction/suggestion + public static final int KIND_COMPLETION = 2; // Completion (suggestion with appended chars) + public static final int KIND_WHITELIST = 3; // Whitelisted word + public static final int KIND_BLACKLIST = 4; // Blacklisted word + public static final int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation + public static final int KIND_APP_DEFINED = 6; // Suggested by the application private final String mWordStr; public final CharSequence mWord; public final int mScore; + public final int mKind; public final int mCodePointCount; private String mDebugString = ""; - public SuggestedWordInfo(final CharSequence word, final int score) { + public SuggestedWordInfo(final CharSequence word, final int score, final int kind) { mWordStr = word.toString(); mWord = word; mScore = score; + mKind = kind; mCodePointCount = mWordStr.codePointCount(0, mWordStr.length()); } diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java index 8a29dcc13..19287e3f3 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java @@ -167,7 +167,7 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel { @Override public boolean dismissMoreKeysPanel() { - if (mIsDismissing) return false; + if (mIsDismissing || mController == null) return false; mIsDismissing = true; final boolean dismissed = mController.dismissMoreKeysPanel(); mIsDismissing = false; diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java index 3d593aaa7..e86390b11 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java @@ -670,7 +670,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, } public void setSuggestions(SuggestedWords suggestedWords) { - if (suggestedWords == null || suggestedWords.size() == 0) + if (suggestedWords == null) return; clear(); @@ -884,5 +884,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, super.onDetachedFromWindow(); mHandler.cancelAllMessages(); hidePreview(); + dismissMoreSuggestions(); } } |