aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/latin/ResearchLogger.java66
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java51
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;
}