Merge "Tentatively remove a dependency on WordCallback (A3)"
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index ffe2b2b..7e6ee0b 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -142,10 +142,10 @@
     <string name="save" msgid="7646738597196767214">"Uložit"</string>
     <string name="subtype_locale" msgid="8576443440738143764">"Jazyk"</string>
     <string name="keyboard_layout_set" msgid="4309233698194565609">"Rozvržení"</string>
-    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Vlastní styl vstupu musíte nejdříve povolit. Povolit?"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Vlastní styl zadávání musíte nejdříve povolit. Povolit?"</string>
     <string name="enable" msgid="5031294444630523247">"Povolit"</string>
     <string name="not_now" msgid="6172462888202790482">"Teď ne"</string>
-    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Stejný styl vstupu již existuje: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Tento styl zadávání již existuje: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Režim studie použitelnosti"</string>
     <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Délka vibrace při stisku klávesy"</string>
     <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Hlasitost při stisknutí klávesy"</string>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index adf51ce..589830d 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -22,14 +22,10 @@
     <bool name="config_use_fullscreen_mode">false</bool>
     <bool name="config_enable_show_voice_key_option">true</bool>
     <bool name="config_enable_show_popup_on_keypress_option">true</bool>
-    <bool name="config_enable_next_word_suggestions_option">true</bool>
     <!-- TODO: Disable the following configuration for production. -->
     <bool name="config_enable_usability_study_mode_option">true</bool>
     <!-- Whether or not Popup on key press is enabled by default -->
     <bool name="config_default_popup_preview">true</bool>
-    <!-- Default value for next word suggestion: while showing suggestions for a word should we weigh
-         in the previous word? -->
-    <bool name="config_default_next_word_suggestions">true</bool>
     <!-- Default value for next word prediction: after entering a word and a space only, should we look
          at input history to suggest a hopefully helpful suggestions for the next word? -->
     <bool name="config_default_next_word_prediction">true</bool>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 1379819..d8bf784 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -115,23 +115,11 @@
                 android:persistent="true"
                 android:defaultValue="true" />
             <CheckBoxPreference
-                android:key="next_word_suggestion"
-                android:title="@string/bigram_suggestion"
-                android:summary="@string/bigram_suggestion_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
-            <CheckBoxPreference
                 android:key="next_word_prediction"
                 android:title="@string/bigram_prediction"
                 android:summary="@string/bigram_prediction_summary"
                 android:persistent="true"
                 android:defaultValue="true" />
-            <CheckBoxPreference
-                android:key="enable_span_insert"
-                android:title="@string/enable_span_insert"
-                android:summary="@string/enable_span_insert_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
             <PreferenceScreen
                 android:key="pref_vibration_duration_settings"
                 android:title="@string/prefs_keypress_vibration_duration_settings"/>
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index b8aab4c..8a5fc49 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1002,8 +1002,7 @@
     }
 
     // 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();
@@ -1958,7 +1957,8 @@
         // expect to receive non-words.
         if (!mCurrentSettings.mCorrectionEnabled) return null;
 
-        if (mUserHistoryDictionary != null) {
+        final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
+        if (userHistoryDictionary != null) {
             final CharSequence prevWord
                     = mConnection.getPreviousWord(mCurrentSettings.mWordSeparators);
             final String secondWord;
@@ -1973,7 +1973,7 @@
             final int maxFreq = AutoCorrection.getMaxFrequency(
                     mSuggest.getUnigramDictionaries(), suggestion);
             if (maxFreq == 0) return null;
-            mUserHistoryDictionary.addToUserHistory(null == prevWord ? null : prevWord.toString(),
+            userHistoryDictionary.addToUserHistory(null == prevWord ? null : prevWord.toString(),
                     secondWord, maxFreq > 0);
             return prevWord;
         }
diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java
index ee59692..cf3cc78 100644
--- a/java/src/com/android/inputmethod/latin/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java
@@ -379,29 +379,43 @@
         mCurrentLogUnit.addLogAtom(keys, values, false);
     }
 
-    private boolean isInDictionary(CharSequence word) {
-        return (mDictionary != null) && (mDictionary.isValidWord(word));
-    }
-
-    /**
-     * Write out enqueued LogEvents to the log, filtered for privacy.
-     *
-     * If word is in the dictionary, then it is not privacy-sensitive and all LogEvents related to
-     * it can be written to the log.  If the word is not in the dictionary, then it may correspond
-     * to a proper name, which might reveal private information, so neither the word nor any
-     * information related to the word (e.g. the down/motion/up coordinates) should be revealed.
-     * These LogEvents have been marked as privacy-sensitive; non privacy-sensitive events are still
-     * written out.
-     *
-     * @param word the word to be checked for inclusion in the dictionary
-     */
-    /* package for test */ synchronized void flushQueue(CharSequence word) {
-        if (isAllowedToLog()) {
-            // check for dictionary
+    /* 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();
             }
-            mCurrentLogUnit.setIsPrivacySafe(word != null && isInDictionary(word));
+            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();
         }
@@ -513,7 +527,7 @@
         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 mIsPrivacySafe = false;
+        private boolean mRemovePotentiallyPrivateEvents = true;
 
         private void addLogAtom(final String[] keys, final Object[] values,
                 final Boolean isPotentiallyPrivate) {
@@ -522,15 +536,15 @@
             mIsPotentiallyPrivate.add(isPotentiallyPrivate);
         }
 
-        void setIsPrivacySafe(boolean isPrivacySafe) {
-            mIsPrivacySafe = isPrivacySafe;
+        void setRemovePotentiallyPrivateEvents(boolean removePotentiallyPrivateEvents) {
+            mRemovePotentiallyPrivateEvents = removePotentiallyPrivateEvents;
         }
 
         @Override
         public void run() {
             final int numAtoms = mKeysList.size();
             for (int atomIndex = 0; atomIndex < numAtoms; atomIndex++) {
-                if (!mIsPrivacySafe && mIsPotentiallyPrivate.get(atomIndex)) {
+                if (mRemovePotentiallyPrivateEvents && mIsPotentiallyPrivate.get(atomIndex)) {
                     continue;
                 }
                 final String[] keys = mKeysList.get(atomIndex);
@@ -548,7 +562,7 @@
         StringBuilder sb = null;
         final int length = s.length();
         for (int i = 0; i < length; i = s.offsetByCodePoints(i, 1)) {
-            int codePoint = Character.codePointAt(s, i);
+            final int codePoint = Character.codePointAt(s, i);
             if (Character.isDigit(codePoint)) {
                 if (sb == null) {
                     sb = new StringBuilder(length);
@@ -638,19 +652,20 @@
         final ResearchLogger researchLogger = getInstance();
         researchLogger.enqueuePotentiallyPrivateEvent(
                 EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
-        researchLogger.flushQueue(autoCorrection);
+        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 = {
-            scrubDigitsFromString(typedWord.toString())
+            scrubbedWord
         };
         final ResearchLogger researchLogger = getInstance();
         researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
-        researchLogger.flushQueue(typedWord);
+        researchLogger.flushQueue(researchLogger.isPrivacyThreat(scrubbedWord));
     }
 
     private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
@@ -728,7 +743,7 @@
             }
             final ResearchLogger researchLogger = getInstance();
             researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values);
-            researchLogger.flushQueue(null);
+            researchLogger.flushQueue(true); // Play it safe.  Remove privacy-sensitive events.
         }
     }
 
@@ -809,7 +824,7 @@
         final ResearchLogger researchLogger = getInstance();
         researchLogger.enqueuePotentiallyPrivateEvent(
                 EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values);
-        researchLogger.flushQueue(cs.toString());
+        researchLogger.flushQueue(researchLogger.isPrivacyThreat(cs.toString()));
     }
 
     private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = {
@@ -824,7 +839,7 @@
         final ResearchLogger researchLogger = getInstance();
         researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY,
                 values);
-        researchLogger.flushQueue(suggestion.toString());
+        researchLogger.flushQueue(researchLogger.isPrivacyThreat(suggestion.toString()));
     }
 
     private static final String[] EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION = {
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 152d668..4c67b49 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -69,9 +69,7 @@
     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 @@
     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 @@
         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 @@
 
         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) {
@@ -149,12 +143,9 @@
             generalSettings.removePreference(mVoicePreference);
         }
 
-        final PreferenceGroup advancedSettings =
-                (PreferenceGroup) findPreference(PREF_ADVANCED_SETTINGS);
-        // Remove those meaningless options for now. TODO: delete them for good
-        advancedSettings.removePreference(findPreference(PREF_BIGRAM_SUGGESTION));
-        advancedSettings.removePreference(findPreference(PREF_KEY_ENABLE_SPAN_INSERT));
         if (!VibratorUtils.getInstance(context).hasVibrator()) {
+            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));
@@ -167,15 +158,6 @@
             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/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 6fa1a25..5bcdb57 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -36,11 +36,22 @@
 
     // TODO: use Words.SHORTCUT when it's public in the SDK
     final static String SHORTCUT = "shortcut";
-    private static final String[] PROJECTION_QUERY = {
-        Words.WORD,
-        SHORTCUT,
-        Words.FREQUENCY,
-    };
+    private static final String[] PROJECTION_QUERY;
+    static {
+        // 16 is JellyBean, but we want this to compile against ICS.
+        if (android.os.Build.VERSION.SDK_INT >= 16) {
+            PROJECTION_QUERY = new String[] {
+                Words.WORD,
+                SHORTCUT,
+                Words.FREQUENCY,
+            };
+        } else {
+            PROJECTION_QUERY = new String[] {
+                Words.WORD,
+                Words.FREQUENCY,
+            };
+        }
+    }
 
     private static final String NAME = "userunigram";
 
@@ -136,7 +147,7 @@
             requestArguments = localeElements;
         }
         final Cursor cursor = mContext.getContentResolver().query(
-            Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
+                Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
         try {
             addWords(cursor);
         } finally {
@@ -182,16 +193,18 @@
     }
 
     private void addWords(Cursor cursor) {
+        // 16 is JellyBean, but we want this to compile against ICS.
+        final boolean hasShortcutColumn = android.os.Build.VERSION.SDK_INT >= 16;
         clearFusionDictionary();
         if (cursor == null) return;
         if (cursor.moveToFirst()) {
             final int indexWord = cursor.getColumnIndex(Words.WORD);
-            final int indexShortcut = cursor.getColumnIndex(SHORTCUT);
+            final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(SHORTCUT) : 0;
             final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
             while (!cursor.isAfterLast()) {
-                String word = cursor.getString(indexWord);
-                String shortcut = cursor.getString(indexShortcut);
-                int frequency = cursor.getInt(indexFrequency);
+                final String word = cursor.getString(indexWord);
+                final String shortcut = hasShortcutColumn ? cursor.getString(indexShortcut) : null;
+                final int frequency = cursor.getInt(indexFrequency);
                 // Safeguard against adding really long words.
                 if (word.length() < MAX_WORD_LENGTH) {
                     super.addWord(word, null, frequency);
diff --git a/tests/src/com/android/inputmethod/latin/PunctuationTests.java b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
index e1d4c46..0eb3ba4 100644
--- a/tests/src/com/android/inputmethod/latin/PunctuationTests.java
+++ b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
@@ -27,7 +27,7 @@
         final String PUNCTUATION_FROM_STRIP = "!";
         final String EXPECTED_RESULT = "this!! ";
         final boolean defaultNextWordPredictionOption =
-                mLatinIME.getResources().getBoolean(R.bool.config_default_next_word_suggestions);
+                mLatinIME.getResources().getBoolean(R.bool.config_default_next_word_prediction);
         final boolean previousNextWordPredictionOption =
                 setBooleanPreference(NEXT_WORD_PREDICTION_OPTION, false,
                         defaultNextWordPredictionOption);