diff options
Diffstat (limited to 'java/src')
-rw-r--r-- | java/src/com/android/inputmethod/latin/ResearchLogger.java | 66 | ||||
-rw-r--r-- | java/src/com/android/inputmethod/latin/Suggest.java | 51 |
2 files changed, 80 insertions, 37 deletions
diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java index cf3cc7873..df8892911 100644 --- a/java/src/com/android/inputmethod/latin/ResearchLogger.java +++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java @@ -197,6 +197,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang Log.d(TAG, "stop called"); if (mLoggingHandler != null && mLoggingState == LOGGING_STATE_ON) { mLoggingState = LOGGING_STATE_STOPPING; + flushEventQueue(true); // put this in the Handler queue so pending writes are processed first. mLoggingHandler.post(new Runnable() { @Override @@ -379,11 +380,52 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mCurrentLogUnit.addLogAtom(keys, values, false); } + // Used to track how often words are logged. Too-frequent logging can leak + // semantics, disclosing private data. + /* package for test */ static class LoggingFrequencyState { + private static final int DEFAULT_WORD_LOG_FREQUENCY = 10; + private int mWordsRemainingToSkip; + private final int mFrequency; + + /** + * Tracks how often words may be uploaded. + * + * @param frequency 1=Every word, 2=Every other word, etc. + */ + public LoggingFrequencyState(int frequency) { + mFrequency = frequency; + mWordsRemainingToSkip = mFrequency; + } + + public void onWordLogged() { + mWordsRemainingToSkip = mFrequency; + } + + public void onWordNotLogged() { + if (mWordsRemainingToSkip > 1) { + mWordsRemainingToSkip--; + } + } + + public boolean isSafeToLog() { + return mWordsRemainingToSkip <= 1; + } + } + + /* package for test */ LoggingFrequencyState mLoggingFrequencyState = + new LoggingFrequencyState(LoggingFrequencyState.DEFAULT_WORD_LOG_FREQUENCY); + /* package for test */ boolean isPrivacyThreat(String word) { - // currently: word not in dictionary or contains numbers. + // Current checks: + // - Word not in dictionary + // - Word contains numbers + // - Privacy-safe word not logged recently if (TextUtils.isEmpty(word)) { return false; } + if (!mLoggingFrequencyState.isSafeToLog()) { + return true; + } final int length = word.length(); boolean hasLetter = false; for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { @@ -410,15 +452,26 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return false; } + private void onWordComplete(String word) { + final boolean isPrivacyThreat = isPrivacyThreat(word); + flushEventQueue(isPrivacyThreat); + if (isPrivacyThreat) { + mLoggingFrequencyState.onWordNotLogged(); + } else { + mLoggingFrequencyState.onWordLogged(); + } + } + /** * Write out enqueued LogEvents to the log, possibly dropping privacy sensitive events. */ - /* package for test */ synchronized void flushQueue(boolean removePotentiallyPrivateEvents) { + /* package for test */ synchronized void flushEventQueue( + boolean removePotentiallyPrivateEvents) { if (isAllowedToLog()) { mCurrentLogUnit.setRemovePotentiallyPrivateEvents(removePotentiallyPrivateEvents); mLoggingHandler.post(mCurrentLogUnit); - mCurrentLogUnit = new LogUnit(); } + mCurrentLogUnit = new LogUnit(); } private synchronized void outputEvent(final String[] keys, final Object[] values) { @@ -652,7 +705,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final ResearchLogger researchLogger = getInstance(); researchLogger.enqueuePotentiallyPrivateEvent( EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values); - researchLogger.flushQueue(researchLogger.isPrivacyThreat(autoCorrection)); } private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = { @@ -665,7 +717,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang }; final ResearchLogger researchLogger = getInstance(); researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values); - researchLogger.flushQueue(researchLogger.isPrivacyThreat(scrubbedWord)); + researchLogger.onWordComplete(scrubbedWord); } private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = { @@ -743,7 +795,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } final ResearchLogger researchLogger = getInstance(); researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values); - researchLogger.flushQueue(true); // Play it safe. Remove privacy-sensitive events. + researchLogger.flushEventQueue(true); // Play it safe. Remove privacy-sensitive events. } } @@ -824,7 +876,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final ResearchLogger researchLogger = getInstance(); researchLogger.enqueuePotentiallyPrivateEvent( EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values); - researchLogger.flushQueue(researchLogger.isPrivacyThreat(cs.toString())); } private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = { @@ -839,7 +890,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final ResearchLogger researchLogger = getInstance(); researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY, values); - researchLogger.flushQueue(researchLogger.isPrivacyThreat(suggestion.toString())); } private static final String[] EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION = { diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index a173d794b..bde3a8403 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -26,6 +26,8 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import java.io.File; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; @@ -384,6 +386,21 @@ public class Suggest { return suggestionsList; } + private static class SuggestedWordInfoComparator implements Comparator<SuggestedWordInfo> { + // This comparator ranks the word info with the higher frequency first. That's because + // that's the order we want our elements in. + @Override + public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) { + if (o1.mScore > o2.mScore) return -1; + if (o1.mScore < o2.mScore) return 1; + if (o1.mCodePointCount < o2.mCodePointCount) return -1; + if (o1.mCodePointCount > o2.mCodePointCount) return 1; + return o1.mWord.toString().compareTo(o2.mWord.toString()); + } + } + private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator = + new SuggestedWordInfoComparator(); + public boolean addWord(final SuggestedWordInfo wordInfo, final int dicTypeId, final int dataType, final ArrayList<SuggestedWordInfo> suggestions, final String consideredWord) { @@ -394,35 +411,11 @@ public class Suggest { final int score = wordInfo.mScore; int pos = 0; - // Check if it's the same word, only caps are different - if (StringUtils.equalsIgnoreCase(consideredWord, word)) { - // TODO: remove this surrounding if clause and move this logic to - // getSuggestedWordBuilder. - if (suggestions.size() > 0) { - final SuggestedWordInfo currentHighestWord = suggestions.get(0); - // If the current highest word is also equal to typed word, we need to compare - // frequency to determine the insertion position. This does not ensure strictly - // correct ordering, but ensures the top score is on top which is enough for - // removing duplicates correctly. - if (StringUtils.equalsIgnoreCase(currentHighestWord.mWord, word) - && score <= currentHighestWord.mScore) { - pos = 1; - } - } - } else { - // Check the last one's score and bail - if (suggestions.size() >= prefMaxSuggestions - && suggestions.get(prefMaxSuggestions - 1).mScore >= score) return true; - final int length = wordInfo.mCodePointCount; - while (pos < suggestions.size()) { - final int curScore = suggestions.get(pos).mScore; - if (curScore < score - || (curScore == score && length < suggestions.get(pos).mCodePointCount)) { - break; - } - pos++; - } - } + final int index = + Collections.binarySearch(suggestions, wordInfo, sSuggestedWordInfoComparator); + // binarySearch returns the index of an equal word info if found. If not found + // it returns -insertionPoint - 1. We want the insertion point, so: + pos = index >= 0 ? index : -index - 1; if (pos >= prefMaxSuggestions) { return true; } |