aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/research/ResearchLogger.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/research/ResearchLogger.java')
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java673
1 files changed, 369 insertions, 304 deletions
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 3cad2d099..cf6f31a0a 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -34,18 +34,15 @@ import android.graphics.Paint.Style;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.IBinder;
-import android.os.SystemClock;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.Button;
@@ -67,8 +64,11 @@ import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.File;
+import java.io.IOException;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
import java.util.Locale;
import java.util.UUID;
@@ -94,21 +94,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
private static final boolean IS_SHOWING_INDICATOR = true;
private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false;
- public static final int FEEDBACK_WORD_BUFFER_SIZE = 5;
// constants related to specific log points
private static final String WHITESPACE_SEPARATORS = " \t\n\r";
private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid";
+ private static final int ABORT_TIMEOUT_IN_MS = 10 * 1000; // timeout to notify user
private static final ResearchLogger sInstance = new ResearchLogger();
// to write to a different filename, e.g., for testing, set mFile before calling start()
/* package */ File mFilesDir;
/* package */ String mUUIDString;
/* package */ ResearchLog mMainResearchLog;
- /* package */ ResearchLog mFeedbackLog;
- /* package */ MainLogBuffer mMainLogBuffer;
- /* package */ LogBuffer mFeedbackLogBuffer;
+ // The mIntentionalResearchLog records all events for the session, private or not (excepting
+ // passwords). It is written to permanent storage only if the user explicitly commands
+ // the system to do so.
+ /* package */ ResearchLog mIntentionalResearchLog;
+ // LogUnits are queued here and released only when the user requests the intentional log.
+ private List<LogUnit> mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
private boolean mIsPasswordView = false;
private boolean mIsLoggingSuspended = false;
@@ -132,13 +135,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private Dictionary mDictionary;
private KeyboardSwitcher mKeyboardSwitcher;
private InputMethodService mInputMethodService;
- private final Statistics mStatistics;
- private ResearchLogUploader mResearchLogUploader;
- private LogUnit mCurrentLogUnit = new LogUnit();
+ private ResearchLogUploader mResearchLogUploader;
private ResearchLogger() {
- mStatistics = Statistics.getInstance();
}
public static ResearchLogger getInstance() {
@@ -259,16 +259,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
e.apply();
}
- private void setLoggingAllowed(boolean enableLogging) {
- if (mPrefs == null) {
- return;
- }
- Editor e = mPrefs.edit();
- e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging);
- e.apply();
- sIsLogging = enableLogging;
- }
-
private File createLogFile(File filesDir) {
final StringBuilder sb = new StringBuilder();
sb.append(FILENAME_PREFIX).append('-');
@@ -278,35 +268,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return new File(filesDir, sb.toString());
}
- private void checkForEmptyEditor() {
- if (mInputMethodService == null) {
- return;
- }
- final InputConnection ic = mInputMethodService.getCurrentInputConnection();
- if (ic == null) {
- return;
- }
- final CharSequence textBefore = ic.getTextBeforeCursor(1, 0);
- if (!TextUtils.isEmpty(textBefore)) {
- mStatistics.setIsEmptyUponStarting(false);
- return;
- }
- final CharSequence textAfter = ic.getTextAfterCursor(1, 0);
- if (!TextUtils.isEmpty(textAfter)) {
- mStatistics.setIsEmptyUponStarting(false);
- return;
- }
- if (textBefore != null && textAfter != null) {
- mStatistics.setIsEmptyUponStarting(true);
- }
- }
-
private void start() {
maybeShowSplashScreen();
updateSuspendedState();
requestIndicatorRedraw();
- mStatistics.reset();
- checkForEmptyEditor();
if (!isAllowedToLog()) {
// Log.w(TAG, "not in usability mode; not logging");
return;
@@ -315,58 +280,73 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
Log.w(TAG, "IME storage directory does not exist. Cannot start logging.");
return;
}
- if (mMainLogBuffer == null) {
- mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
- mMainLogBuffer = new MainLogBuffer(mMainResearchLog);
- mMainLogBuffer.setSuggest(mSuggest);
- }
- if (mFeedbackLogBuffer == null) {
- mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
- // LogBuffer is one more than FEEDBACK_WORD_BUFFER_SIZE, because it must also hold
- // the feedback LogUnit itself.
- mFeedbackLogBuffer = new LogBuffer(FEEDBACK_WORD_BUFFER_SIZE + 1);
+ try {
+ if (mMainResearchLog == null || !mMainResearchLog.isAlive()) {
+ mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
+ }
+ mMainResearchLog.start();
+ if (mIntentionalResearchLog == null || !mIntentionalResearchLog.isAlive()) {
+ mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
+ }
+ mIntentionalResearchLog.start();
+ } catch (IOException e) {
+ Log.w(TAG, "Could not start ResearchLogger.");
}
}
/* package */ void stop() {
- logStatistics();
- commitCurrentLogUnit();
-
- if (mMainLogBuffer != null) {
- publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */);
- mMainResearchLog.close();
- mMainLogBuffer = null;
+ if (mMainResearchLog != null) {
+ mMainResearchLog.stop();
}
- if (mFeedbackLogBuffer != null) {
- mFeedbackLog.close();
- mFeedbackLogBuffer = null;
+ if (mIntentionalResearchLog != null) {
+ mIntentionalResearchLog.stop();
}
}
+ private void setLoggingAllowed(boolean enableLogging) {
+ if (mPrefs == null) {
+ return;
+ }
+ Editor e = mPrefs.edit();
+ e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging);
+ e.apply();
+ sIsLogging = enableLogging;
+ }
+
public boolean abort() {
boolean didAbortMainLog = false;
- if (mMainLogBuffer != null) {
- mMainLogBuffer.clear();
+ if (mMainResearchLog != null) {
+ mMainResearchLog.abort();
try {
- didAbortMainLog = mMainResearchLog.blockingAbort();
+ mMainResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS);
} catch (InterruptedException e) {
- // Don't know whether this succeeded or not. We assume not; this is reported
- // to the caller.
+ // interrupted early. carry on.
}
- mMainLogBuffer = null;
+ if (mMainResearchLog.isAbortSuccessful()) {
+ didAbortMainLog = true;
+ }
+ mMainResearchLog = null;
}
- boolean didAbortFeedbackLog = false;
- if (mFeedbackLogBuffer != null) {
- mFeedbackLogBuffer.clear();
+ boolean didAbortIntentionalLog = false;
+ if (mIntentionalResearchLog != null) {
+ mIntentionalResearchLog.abort();
try {
- didAbortFeedbackLog = mFeedbackLog.blockingAbort();
+ mIntentionalResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS);
} catch (InterruptedException e) {
- // Don't know whether this succeeded or not. We assume not; this is reported
- // to the caller.
+ // interrupted early. carry on.
+ }
+ if (mIntentionalResearchLog.isAbortSuccessful()) {
+ didAbortIntentionalLog = true;
}
- mFeedbackLogBuffer = null;
+ mIntentionalResearchLog = null;
+ }
+ return didAbortMainLog && didAbortIntentionalLog;
+ }
+
+ /* package */ void flush() {
+ if (mMainResearchLog != null) {
+ mMainResearchLog.flush();
}
- return didAbortMainLog && didAbortFeedbackLog;
}
private void restart() {
@@ -470,39 +450,79 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
latinIME.launchKeyboardedDialogActivity(FeedbackActivity.class);
}
- private static final String[] EVENTKEYS_FEEDBACK = {
- "UserTimestamp", "contents"
- };
- public void sendFeedback(final String feedbackContents, final boolean includeHistory) {
- if (mFeedbackLogBuffer == null) {
- return;
+ private ResearchLog mFeedbackLog;
+ private List<LogUnit> mFeedbackQueue;
+ private ResearchLog mSavedMainResearchLog;
+ private ResearchLog mSavedIntentionalResearchLog;
+ private List<LogUnit> mSavedIntentionalResearchLogQueue;
+
+ private void saveLogsForFeedback() {
+ mFeedbackLog = mIntentionalResearchLog;
+ if (mIntentionalResearchLogQueue != null) {
+ mFeedbackQueue = new ArrayList<LogUnit>(mIntentionalResearchLogQueue);
+ } else {
+ mFeedbackQueue = null;
}
- if (!includeHistory) {
- mFeedbackLogBuffer.clear();
+ mSavedMainResearchLog = mMainResearchLog;
+ mSavedIntentionalResearchLog = mIntentionalResearchLog;
+ mSavedIntentionalResearchLogQueue = mIntentionalResearchLogQueue;
+
+ mMainResearchLog = null;
+ mIntentionalResearchLog = null;
+ mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
+ }
+
+ private static final int LOG_DRAIN_TIMEOUT_IN_MS = 1000 * 5;
+ public void sendFeedback(final String feedbackContents, final boolean includeHistory) {
+ if (includeHistory && mFeedbackLog != null) {
+ try {
+ LogUnit headerLogUnit = new LogUnit();
+ headerLogUnit.addLogAtom(EVENTKEYS_INTENTIONAL_LOG, EVENTKEYS_NULLVALUES, false);
+ mFeedbackLog.publishAllEvents(headerLogUnit);
+ for (LogUnit logUnit : mFeedbackQueue) {
+ mFeedbackLog.publishAllEvents(logUnit);
+ }
+ userFeedback(mFeedbackLog, feedbackContents);
+ mFeedbackLog.stop();
+ try {
+ mFeedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
+ mIntentionalResearchLog.start();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ mIntentionalResearchLogQueue.clear();
+ }
+ mResearchLogUploader.uploadNow(null);
+ } else {
+ // create a separate ResearchLog just for feedback
+ final ResearchLog feedbackLog = new ResearchLog(createLogFile(mFilesDir));
+ try {
+ feedbackLog.start();
+ userFeedback(feedbackLog, feedbackContents);
+ feedbackLog.stop();
+ feedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS);
+ mResearchLogUploader.uploadNow(null);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
}
- commitCurrentLogUnit();
- final LogUnit feedbackLogUnit = new LogUnit();
- final Object[] values = {
- feedbackContents
- };
- feedbackLogUnit.addLogStatement(EVENTKEYS_FEEDBACK, values,
- false /* isPotentiallyPrivate */);
- mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
- publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */);
- mFeedbackLog.close();
- mResearchLogUploader.uploadAfterCompletion(mFeedbackLog, null);
- mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
}
public void onLeavingSendFeedbackDialog() {
mInFeedbackDialog = false;
+ mMainResearchLog = mSavedMainResearchLog;
+ mIntentionalResearchLog = mSavedIntentionalResearchLog;
+ mIntentionalResearchLogQueue = mSavedIntentionalResearchLogQueue;
}
public void initSuggest(Suggest suggest) {
mSuggest = suggest;
- if (mMainLogBuffer != null) {
- mMainLogBuffer.setSuggest(mSuggest);
- }
}
private void setIsPasswordView(boolean isPasswordView) {
@@ -510,7 +530,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
private boolean isAllowedToLog() {
- return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog;
+ return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging;
}
public void requestIndicatorRedraw() {
@@ -557,8 +577,13 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
+ 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();
+
/**
* Buffer a research log event, flagging it as privacy-sensitive.
*
@@ -574,14 +599,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final Object[] values) {
assert values.length + 1 == keys.length;
if (isAllowedToLog()) {
- mCurrentLogUnit.addLogStatement(keys, values, true /* isPotentiallyPrivate */);
+ mCurrentLogUnit.addLogAtom(keys, values, true);
}
}
- private void setCurrentLogUnitContainsDigitFlag() {
- mCurrentLogUnit.setContainsDigit();
- }
-
/**
* Buffer a research log event, flaggint it as not privacy-sensitive.
*
@@ -597,54 +618,139 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private synchronized void enqueueEvent(final String[] keys, final Object[] values) {
assert values.length + 1 == keys.length;
if (isAllowedToLog()) {
- mCurrentLogUnit.addLogStatement(keys, values, false /* isPotentiallyPrivate */);
+ mCurrentLogUnit.addLogAtom(keys, values, false);
}
}
- /* package for test */ void commitCurrentLogUnit() {
- if (!mCurrentLogUnit.isEmpty()) {
- if (mMainLogBuffer != null) {
- mMainLogBuffer.shiftIn(mCurrentLogUnit);
- if (mMainLogBuffer.isSafeToLog() && mMainResearchLog != null) {
- publishLogBuffer(mMainLogBuffer, mMainResearchLog,
- true /* isIncludingPrivateData */);
- mMainLogBuffer.resetWordCounter();
- }
- }
- if (mFeedbackLogBuffer != null) {
- mFeedbackLogBuffer.shiftIn(mCurrentLogUnit);
+ // 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--;
}
- mCurrentLogUnit = new LogUnit();
- Log.d(TAG, "commitCurrentLogUnit");
}
- }
- /* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
- final ResearchLog researchLog, final boolean isIncludingPrivateData) {
- LogUnit logUnit;
- while ((logUnit = logBuffer.shiftOut()) != null) {
- researchLog.publish(logUnit, isIncludingPrivateData);
+ public boolean isSafeToLog() {
+ return mWordsRemainingToSkip <= 1;
}
}
- private boolean hasOnlyLetters(final String word) {
+ /* package for test */ LoggingFrequencyState mLoggingFrequencyState =
+ new LoggingFrequencyState(LoggingFrequencyState.DEFAULT_WORD_LOG_FREQUENCY);
+
+ /* package for test */ boolean isPrivacyThreat(String word) {
+ // 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)) {
- final int codePoint = word.codePointAt(i);
- if (!Character.isLetter(codePoint)) {
- return false;
+ 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));
}
- return true;
+ // No letters, no numbers. Punctuation, space, or something else.
+ return false;
}
- private void onWordComplete(final String word) {
- Log.d(TAG, "onWordComplete: " + word);
- if (word != null && word.length() > 0 && hasOnlyLetters(word)) {
- mCurrentLogUnit.setWord(word);
- mStatistics.recordWordEntered();
+ private void onWordComplete(String word) {
+ if (isPrivacyThreat(word)) {
+ publishLogUnit(mCurrentLogUnit, true);
+ mLoggingFrequencyState.onWordNotLogged();
+ } else {
+ publishLogUnit(mCurrentLogUnit, false);
+ mLoggingFrequencyState.onWordLogged();
+ }
+ mCurrentLogUnit = new LogUnit();
+ }
+
+ private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) {
+ if (!isAllowedToLog()) {
+ return;
+ }
+ if (mMainResearchLog == null) {
+ return;
+ }
+ if (isPrivacySensitive) {
+ mMainResearchLog.publishPublicEvents(logUnit);
+ } else {
+ mMainResearchLog.publishAllEvents(logUnit);
+ }
+ mIntentionalResearchLogQueue.add(logUnit);
+ }
+
+ /* package */ void publishCurrentLogUnit(ResearchLog researchLog, boolean isPrivacySensitive) {
+ publishLogUnit(mCurrentLogUnit, isPrivacySensitive);
+ }
+
+ static class LogUnit {
+ 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 void addLogAtom(final String[] keys, final Object[] values,
+ final Boolean isPotentiallyPrivate) {
+ mKeysList.add(keys);
+ mValuesList.add(values);
+ mIsPotentiallyPrivate.add(isPotentiallyPrivate);
+ }
+
+ public void publishPublicEventsTo(ResearchLog researchLog) {
+ final int size = mKeysList.size();
+ for (int i = 0; i < size; i++) {
+ if (!mIsPotentiallyPrivate.get(i)) {
+ researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
+ }
+ }
+ }
+
+ public void publishAllEventsTo(ResearchLog researchLog) {
+ final int size = mKeysList.size();
+ for (int i = 0; i < size; i++) {
+ researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
+ }
}
- commitCurrentLogUnit();
}
private static int scrubDigitFromCodePoint(int codePoint) {
@@ -697,6 +803,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return WORD_REPLACEMENT_STRING;
}
+ // Special methods related to startup, shutdown, logging itself
+
+ private static final String[] EVENTKEYS_INTENTIONAL_LOG = {
+ "IntentionalLog"
+ };
+
private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
"LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
"fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
@@ -704,6 +816,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
final ResearchLogger researchLogger = getInstance();
+ if (researchLogger.mInFeedbackDialog) {
+ researchLogger.saveLogsForFeedback();
+ }
researchLogger.start();
if (editorInfo != null) {
final Context context = researchLogger.mInputMethodService;
@@ -731,10 +846,34 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
stop();
}
+ 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 = {
+ scrubbedWord
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
+ researchLogger.onWordComplete(scrubbedWord);
+ }
+
private static final String[] EVENTKEYS_USER_FEEDBACK = {
"UserFeedback", "FeedbackContents"
};
+ private void userFeedback(ResearchLog researchLog, String feedbackContents) {
+ // this method is special; it directs the feedbackContents to a particular researchLog
+ final LogUnit logUnit = new LogUnit();
+ final Object[] values = {
+ feedbackContents
+ };
+ logUnit.addLogAtom(EVENTKEYS_USER_FEEDBACK, values, false);
+ researchLog.publishAllEvents(logUnit);
+ }
+
// Regular logging methods
private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_PROCESSMOTIONEVENT = {
@@ -769,16 +908,51 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
"LatinIMEOnCodeInput", "code", "x", "y"
};
public static void latinIME_onCodeInput(final int code, final int x, final int y) {
- final long time = SystemClock.uptimeMillis();
- final ResearchLogger researchLogger = getInstance();
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
};
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
- if (Character.isDigit(code)) {
- researchLogger.setCurrentLogUnitContainsDigitFlag();
- }
- researchLogger.mStatistics.recordChar(code, time);
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
+ }
+
+ private static final String[] EVENTKEYS_CORRECTION = {
+ "LogCorrection", "subgroup", "before", "after", "position"
+ };
+ public static void logCorrection(final String subgroup, final String before, final String after,
+ final int position) {
+ final Object[] values = {
+ subgroup, scrubDigitsFromString(before), scrubDigitsFromString(after), position
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_CORRECTION, values);
+ }
+
+ private static final String[] EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION = {
+ "LatinIMECommitCurrentAutoCorrection", "typedWord", "autoCorrection"
+ };
+ public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
+ final String autoCorrection) {
+ final Object[] values = {
+ scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
+ }
+
+ private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
+ "LatinIMEDeleteSurroundingText", "length"
+ };
+ public static void latinIME_deleteSurroundingText(final int length) {
+ final Object[] values = {
+ length
+ };
+ getInstance().enqueueEvent(EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT, values);
+ }
+
+ private static final String[] EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD = {
+ "LatinIMEDoubleSpaceAutoPeriod"
+ };
+ public static void latinIME_doubleSpaceAutoPeriod() {
+ getInstance().enqueueEvent(EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD, EVENTKEYS_NULLVALUES);
}
private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = {
@@ -805,10 +979,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static void latinIME_onWindowHidden(final int savedSelectionStart,
final int savedSelectionEnd, final InputConnection ic) {
if (ic != null) {
- // Capture the TextView contents. This will trigger onUpdateSelection(), so we
- // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called,
- // it can tell that it was generated by the logging code, and not by the user, and
- // therefore keep user-visible state as is.
ic.beginBatchEdit();
ic.performContextMenuAction(android.R.id.selectAll);
CharSequence charSequence = ic.getSelectedText(0);
@@ -843,7 +1013,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values);
- researchLogger.commitCurrentLogUnit();
+ // Play it safe. Remove privacy-sensitive events.
+ researchLogger.publishLogUnit(researchLogger.mCurrentLogUnit, true);
+ researchLogger.mCurrentLogUnit = new LogUnit();
getInstance().stop();
}
}
@@ -876,6 +1048,29 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values);
}
+ private static final String[] EVENTKEYS_LATINIME_PERFORMEDITORACTION = {
+ "LatinIMEPerformEditorAction", "imeActionNext"
+ };
+ public static void latinIME_performEditorAction(final int imeActionNext) {
+ final Object[] values = {
+ imeActionNext
+ };
+ getInstance().enqueueEvent(EVENTKEYS_LATINIME_PERFORMEDITORACTION, values);
+ }
+
+ private static final String[] EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION = {
+ "LatinIMEPickApplicationSpecifiedCompletion", "index", "text", "x", "y"
+ };
+ public static void latinIME_pickApplicationSpecifiedCompletion(final int index,
+ final CharSequence cs, int x, int y) {
+ final Object[] values = {
+ index, cs, x, y
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values);
+ }
+
private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = {
"LatinIMEPickSuggestionManually", "replacedWord", "index", "suggestion", "x", "y"
};
@@ -901,6 +1096,21 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
getInstance().enqueueEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values);
}
+ private static final String[] EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT = {
+ "LatinIMERevertDoubleSpaceWhileInBatchEdit"
+ };
+ public static void latinIME_revertDoubleSpaceWhileInBatchEdit() {
+ getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT,
+ EVENTKEYS_NULLVALUES);
+ }
+
+ private static final String[] EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION = {
+ "LatinIMERevertSwapPunctuation"
+ };
+ public static void latinIME_revertSwapPunctuation() {
+ getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION, EVENTKEYS_NULLVALUES);
+ }
+
private static final String[] EVENTKEYS_LATINIME_SENDKEYCODEPOINT = {
"LatinIMESendKeyCodePoint", "code"
};
@@ -908,18 +1118,15 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code))
};
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
- if (Character.isDigit(code)) {
- researchLogger.setCurrentLogUnitContainsDigitFlag();
- }
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
}
- private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE = {
- "LatinIMESwapSwapperAndSpace"
+ private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT = {
+ "LatinIMESwapSwapperAndSpaceWhileInBatchEdit"
};
- public static void latinIME_swapSwapperAndSpace() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE, EVENTKEYS_NULLVALUES);
+ public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() {
+ getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT,
+ EVENTKEYS_NULLVALUES);
}
private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS = {
@@ -1038,128 +1245,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values);
}
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION = {
- "RichInputConnectionCommitCompletion", "completionInfo"
- };
- public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) {
- final Object[] values = {
- completionInfo
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION, values);
- }
-
- // Disabled for privacy-protection reasons. Because this event comes after
- // richInputConnection_commitText, which is the event used to separate LogUnits, the
- // data in this event can be associated with the next LogUnit, revealing information
- // about the current word even if it was supposed to be suppressed. The occurrance of
- // autocorrection can be determined by examining the difference between the text strings in
- // the last call to richInputConnection_setComposingText before
- // richInputConnection_commitText, so it's not a data loss.
- // TODO: Figure out how to log this event without loss of privacy.
- /*
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION = {
- "RichInputConnectionCommitCorrection", "typedWord", "autoCorrection"
- };
- */
- public static void richInputConnection_commitCorrection(CorrectionInfo correctionInfo) {
- /*
- final String typedWord = correctionInfo.getOldText().toString();
- final String autoCorrection = correctionInfo.getNewText().toString();
- final Object[] values = {
- scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION, values);
- */
- }
-
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT = {
- "RichInputConnectionCommitText", "typedWord", "newCursorPosition"
- };
- public static void richInputConnection_commitText(final CharSequence typedWord,
- final int newCursorPosition) {
- final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
- final Object[] values = {
- scrubbedWord, newCursorPosition
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT,
- values);
- researchLogger.onWordComplete(scrubbedWord);
- }
-
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = {
- "RichInputConnectionDeleteSurroundingText", "beforeLength", "afterLength"
- };
- public static void richInputConnection_deleteSurroundingText(final int beforeLength,
- final int afterLength) {
- final Object[] values = {
- beforeLength, afterLength
- };
- getInstance().enqueuePotentiallyPrivateEvent(
- EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT, values);
- }
-
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT = {
- "RichInputConnectionFinishComposingText"
- };
- public static void richInputConnection_finishComposingText() {
- getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT,
- EVENTKEYS_NULLVALUES);
- }
-
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION = {
- "RichInputConnectionPerformEditorAction", "imeActionNext"
- };
- public static void richInputConnection_performEditorAction(final int imeActionNext) {
- final Object[] values = {
- imeActionNext
- };
- getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION, values);
- }
-
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT = {
- "RichInputConnectionSendKeyEvent", "eventTime", "action", "code"
- };
- public static void richInputConnection_sendKeyEvent(final KeyEvent keyEvent) {
- final Object[] values = {
- keyEvent.getEventTime(),
- keyEvent.getAction(),
- keyEvent.getKeyCode()
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT,
- values);
- }
-
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT = {
- "RichInputConnectionSetComposingText", "text", "newCursorPosition"
- };
- public static void richInputConnection_setComposingText(final CharSequence text,
- final int newCursorPosition) {
- if (text == null) {
- throw new RuntimeException("setComposingText is null");
- }
- final Object[] values = {
- text, newCursorPosition
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT,
- values);
- }
-
- private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION = {
- "RichInputConnectionSetSelection", "from", "to"
- };
- public static void richInputConnection_setSelection(final int from, final int to) {
- final Object[] values = {
- from, to
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION,
- values);
- }
-
private static final String[] EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = {
"SuddenJumpingTouchEventHandlerOnTouchEvent", "motionEvent"
};
@@ -1192,24 +1277,4 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public void userTimestamp() {
getInstance().enqueueEvent(EVENTKEYS_USER_TIMESTAMP, EVENTKEYS_NULLVALUES);
}
-
- private static final String[] EVENTKEYS_STATISTICS = {
- "Statistics", "charCount", "letterCount", "numberCount", "spaceCount", "deleteOpsCount",
- "wordCount", "isEmptyUponStarting", "isEmptinessStateKnown", "averageTimeBetweenKeys",
- "averageTimeBeforeDelete", "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete"
- };
- private static void logStatistics() {
- final ResearchLogger researchLogger = getInstance();
- final Statistics statistics = researchLogger.mStatistics;
- final Object[] values = {
- statistics.mCharCount, statistics.mLetterCount, statistics.mNumberCount,
- statistics.mSpaceCount, statistics.mDeleteKeyCount,
- statistics.mWordCount, statistics.mIsEmptyUponStarting,
- statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
- statistics.mBeforeDeleteKeyCounter.getAverageTime(),
- statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
- statistics.mAfterDeleteKeyCounter.getAverageTime()
- };
- researchLogger.enqueueEvent(EVENTKEYS_STATISTICS, values);
- }
}